Skip to content

VelvetUi

SolidJS UI component library and shared frontend tooling standards. Published to GitHub Packages as @jankrajewskiit/ui.

Install

npm install @jankrajewskiit/ui solid-js @solidjs/router

For GitHub Packages authentication, add to .npmrc:

//npm.pkg.github.com/:_authToken=${GHCR_TOKEN}
@jankrajewskiit:registry=https://npm.pkg.github.com/

Quick Start

import '@jankrajewskiit/ui/styles';
import { Button, ThemeProvider } from '@jankrajewskiit/ui';

const App = () => (
  <ThemeProvider>
    <Button variant="primary">Save</Button>
  </ThemeProvider>
);

Shared Tooling Presets

VelvetUi exports configuration presets to enforce consistent standards across SolidJS projects.

ESLint

// eslint.config.mjs
import { createEslintConfig } from '@jankrajewskiit/ui/eslint-config';

export default createEslintConfig({
  tsconfigRootDir: import.meta.dirname,
});

Prettier

// .prettierrc
"@jankrajewskiit/ui/prettier-config"

TypeScript

// tsconfig.app.json
{
  "extends": "@jankrajewskiit/ui/tsconfig/app-vite",
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "baseUrl": ".",
    "paths": { "~/*": ["src/*"] }
  },
  "include": ["src"]
}
// tsconfig.node.json
{
  "extends": "@jankrajewskiit/ui/tsconfig/node-vite",
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
  },
  "include": ["vite.config.ts"]
}

Vite

// vite.config.ts
import { defineConfig } from 'vite';
import { createSolidAppViteConfig } from '@jankrajewskiit/ui/vite-config';

export default defineConfig(
  createSolidAppViteConfig({
    server: { port: 5173 },
    preview: { port: 5173 },
  })
);

createSolidAppViteConfig applies defaults for vite-plugin-solid, alias ~ -> <project>/src, and solid-markdown optimizeDeps.

lint-staged

// .lintstagedrc.mjs
import { createLintStagedConfig } from '@jankrajewskiit/ui/lint-staged-config';

export default createLintStagedConfig();

Husky pre-commit

# .husky/pre-commit
. "$(dirname -- "$0")/../node_modules/@jankrajewskiit/ui/config/husky-pre-commit.sh"

Component Catalog

Actions and Forms

ActionRow, Autocomplete, Button, Checkbox, ColorPicker, DropdownButton, DropdownItem, DropdownMenu, FormField, FormGrid, FormLayout, IconButton, Input, LocaleSwitcher, RadioGroup, Select, Switch, TagInput, TextArea

Layouts

Footer, MainLayout, Navbar

Feedback and States

AccessDenied, Chip, EmptyState, ErrorFallback, ErrorMessage, LoadingContainer, Skeleton, Spinner, StatCard, TagChip, ThemeToggle

Overlays

ConfirmAction, ConfirmDialog, Dialog, DialogActions, DialogBody, DialogTitle, Drawer, Tooltip

Data Display

Accordion, AccordionItem, JsonDisplay, MarkdownRenderer, Pagination, RadarChart, ScoreRing, SectionCard, Stepper, Table, TableBody, TableCell, TableHead, TableHeader, TableRow

Workflows

AiResultPanel, AuthRedirect, EditableCollection, FileDropZone, FlipCard, MarkdownEditDialog, RequireAuth, Tabs, Timeline, Wizard

Theme, Hooks, Utilities

Category Exports
Theme ThemeProvider, useTheme, ThemeChoice
Toast ToastProvider, ToastViewport, useToast, addToast, removeToast, ToastMessage
Hooks useClickOutside, useEscapeKey
Utils getContrastTextColor, orEmpty, numberToString, emptyToNull, parseFloatOrNull
Types Props, NavItem, RadioOption, WizardStep

Source Structure

src/
  components/
    actions-and-forms/
    layouts/
    feedback-and-states/
    overlays/
    data-display/
    workflows/
  hooks/
  models/
  theme/
  utils/
  index.ts
config/
demo/

Design Principles

Components are generic and app-agnostic:

  • No i18n calls — all text passed via props
  • No auth calls — consumers handle auth at the call site
  • Layout components use JSX slots for composition
  • Every component forwards class?: string via Props<T>

Scripts

npm run typecheck   # TypeScript check
npm run lint        # ESLint
npm run build       # Build library
npm run test        # Run unit tests
npm run verify      # typecheck + lint + build + test

CI/CD

Publishing

Library is published to GitHub Packages on tags matching v* via .github/workflows/publish.yml.

Demo Deployment

The demo app deploys to https://demo.bluebraces.online on every push to main via .github/workflows/deploy-demo.yml.

Pipeline: verify library → build Docker image → push to GHCR → deploy via SSH

The demo container joins the aspire Docker network on deployment. Nginx routes demo.bluebraces.online to the velvet-demo container (config maintained in Recron at deploy/nginx/nginx.conf).

Required Secrets (GitHub Environment: production)

Secret Description
VPS_HOST Hostinger VPS IP address
VPS_USER SSH username (root)
VPS_SSH_KEY Ed25519 private SSH key
GHCR_TOKEN GitHub PAT with read:packages + write:packages

Generating VPS_SSH_KEY

Windows (PowerShell):

ssh-keygen -t ed25519 -C "github-actions-deploy" -f $env:USERPROFILE\.ssh\velvet_deploy

Get-Content $env:USERPROFILE\.ssh\velvet_deploy.pub | ssh root@YOUR_VPS_IP "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

# Display private key to copy to GitHub Secrets
Get-Content $env:USERPROFILE\.ssh\velvet_deploy

Linux/macOS:

ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/velvet_deploy -N ""
ssh-copy-id -i ~/.ssh/velvet_deploy.pub root@YOUR_VPS_IP
cat ~/.ssh/velvet_deploy

Copy the entire private key (including -----BEGIN OPENSSH PRIVATE KEY----- header/footer) into VPS_SSH_KEY.

If the VPS is already configured for another project, the SSH key may already be in authorized_keys. Reuse the existing private key value.

Generating GHCR_TOKEN

  1. GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
  2. Generate new token, set expiration (90 days recommended)
  3. Select scopes: read:packages, write:packages
  4. Add as GHCR_TOKEN in the production environment secrets

DNS

Add an A record in Hostinger hPanel (DNS Zone Editor):

Type Name Target
A demo VPS IP address

SSL Certificate

The demo runs behind Traefik and shares the wildcard certificate. If the certificate was issued before demo.bluebraces.online was added, expand it on the VPS — see Deployment runbook.