Design system
Living style guide for NVX Stack Builder: semantic design tokens (light + dark) and accessible, headless-based components. Everything below is rendered with the real components — switch theme or language in the header to see them adapt.
Source: src/app/tokens.css · src/components/ui/ · docs/UI-FRAMEWORK.md
Colour
Semantic colour tokens. Components never use raw palette colours, so dark mode is just a different set of variable values.
Surfaces & text
- --nvx-bgPage background ·
bg-bg - --nvx-surfaceCards, inputs ·
bg-surface - --nvx-surface-2Hover, subtle fills ·
bg-surface-2 - --nvx-surface-3Pressed, skeleton ·
bg-surface-3 - --nvx-borderDefault border ·
border-border - --nvx-border-strongInputs, emphasis ·
border-border-strong - --nvx-fgPrimary text ·
text-fg - --nvx-mutedSecondary text ·
text-muted - --nvx-subtleHints, captions ·
text-subtle
Brand
- --nvx-primaryPrimary actions ·
bg-primary - --nvx-primary-hoverPrimary hover ·
hover:bg-primary-hover - --nvx-primary-softSelected, soft fills ·
bg-primary-soft - --nvx-primary-soft-fgText on soft fill ·
text-primary-soft-fg - --nvx-accentSecondary brand ·
text-accent - --nvx-ringFocus ring ·
ring-ring
Status
- --nvx-successSuccess ·
text-success - --nvx-success-softSuccess background ·
bg-success-soft - --nvx-warningWarning ·
text-warning - --nvx-warning-softWarning background ·
bg-warning-soft - --nvx-dangerDestructive / error ·
bg-danger - --nvx-danger-softError background ·
bg-danger-soft - --nvx-infoInfo ·
text-info - --nvx-info-softInfo background ·
bg-info-soft - --nvx-code-bgCode blocks (always dark) ·
bg-code-bg
Usage
<div className="bg-surface text-fg border border-border">…</div>
<p className="text-muted">Secondary text</p>
<button className="bg-primary text-primary-fg hover:bg-primary-hover">…</button>Typography
Geist for Latin, IBM Plex Sans Thai for Thai (automatic per-glyph fallback in one font stack), Geist Mono for code. Thai needs generous line-height — body text uses Tailwind's default 1.5.
- text-display · 52pxRunnable stacksDisplay — hero headline
- text-4xl · 36pxRunnable stacksPage title (h1)
- text-2xl · 24pxRunnable stacksSection title (h2)
- text-xl · 20pxRunnable stacksStage / card group title
- text-base · 16pxRunnable stacksBody, card titles
- text-sm · 14pxRunnable stacksUI text, descriptions
- text-xs · 12pxRunnable stacksCaptions, badges, hints
Sans · The quick brown fox · ภาษาไทยอ่านง่าย สบายตา ๑๒๓
Mono · npx create-next-app@latest --ts
Usage
<h1 className="text-4xl font-bold tracking-tight">…</h1>
<p className="text-sm text-muted">…</p>
<code className="font-mono">npm install</code>Spacing & radius
A 4px spacing unit (p-1 = 4px). Prefer 2, 3, 4, 6, 8 for UI gaps. Radius scales with the size of the element: md for controls, xl for cards, full for pills.
sm
md
lg
xl
full
Elevation & motion
Three shadow levels: sm (resting cards), md (hover), lg (dialogs, toasts). Motion uses three durations and two easings; all animation is disabled under prefers-reduced-motion.
--nvx-duration-fast120msHover, press--nvx-duration-base200msDialogs, fades--nvx-duration-slow320msSheets, toasts--nvx-ease-standardcubic-bezier(0.2, 0, 0, 1)Most transitions--nvx-ease-emphasizedcubic-bezier(0.3, 0, 0, 1.2)Entrances with a little overshoot
Input, Textarea, Select
Native form controls with token styling. Always pair with a visible label — `Field` wires up id, hint and error (aria-describedby, aria-invalid, role=alert).
a-z 0-9 - _ .
Enter a valid email address
Usage
<Field label="Email" hint="We never share it" error={error}>
{(p) => <Input {...p} invalid={!!error} type="email" />}
</Field>Checkbox, Switch, Segmented
Checkbox and Switch are Radix primitives (keyboard + screen-reader ready). Segmented is a styled native radio group: arrow keys move the selection.
Lint + test on every push
not available for this template
Usage
<Checkbox label="Docker" checked={on} onCheckedChange={setOn} />
<Switch label="Dark mode" checked={dark} onCheckedChange={setDark} />
<Segmented legend="Package manager" value={pm} onChange={setPm}
options={[{ value: "npm", label: "npm" }, { value: "pnpm", label: "pnpm" }]} />Card
The main container. Compose with CardHeader/Title/Description/Content/Footer. `interactive` adds hover lift for clickable cards; pick the heading level with `as`.
Next.js Dashboard
Static card with header, content and footer.
interactive
Hover me — lifts with shadow-md.
Usage
<Card interactive>
<CardHeader><CardTitle as="h3">Title</CardTitle><CardDescription>…</CardDescription></CardHeader>
<CardContent>…</CardContent>
<CardFooter><Button size="sm">Action</Button></CardFooter>
</Card>Badge
Short status or metadata labels. Colour is never the only signal — the text carries the meaning.
Usage
<Badge variant="success">AI plan</Badge>
<Badge size="sm">tag</Badge>Tabs
Radix Tabs with roving focus: ←/→ switch tabs, Tab moves into the panel. Used for “Ask AI / Pick template” in the builder.
Usage
<Tabs defaultValue="a">
<TabsList aria-label="Start"><TabsTrigger value="a">A</TabsTrigger><TabsTrigger value="b">B</TabsTrigger></TabsList>
<TabsContent value="a">…</TabsContent>
</Tabs>Dialog & Sheet
Modal dialog with focus trap, Escape to close, scroll lock and focus return. `side="right"` turns it into a sheet — the mobile navigation uses it.
Usage
<Dialog>
<DialogTrigger asChild><Button>Open</Button></DialogTrigger>
<DialogContent title="Title" description="…">…</DialogContent>
</Dialog>Toast
Non-blocking feedback for copy, download, add/remove. Announced politely (errors assertively), swipe or ✕ to dismiss, auto-hide after 3.5 s and paused on hover/focus.
Usage
const toast = useToast();
toast({ title: "Copied", description: "12 commands", variant: "success" });Tooltip & Kbd
Tooltips supplement — never replace — an accessible name. They open on hover and keyboard focus. Kbd renders keyboard shortcuts.
Usage
<Tooltip content="Move up"><Button size="icon" aria-label="Move up">↑</Button></Tooltip>
<Kbd>Ctrl</Kbd> <Kbd>K</Kbd>CodeBlock
Always-dark code surface with a labelled copy button, optional title and shell prompt (prompt glyphs are not copied). Fires a toast when inside ToastProvider.
nvm install --ltsnpm install rechartsnpm run devexport default function Page() {
return <h1>Hello NVX</h1>;
}Usage
<CodeBlock code={"npm install\nnpm run dev"} prompt title="bash" toastTitle="Copied" />Stepper
Shows progress through a multi-step flow. The current step has aria-current="step"; completed steps show ✓. Clickable when onStepChange is set. Vertical on mobile.
Usage
<Stepper current={step} onStepChange={setStep}
items={[{ id: "start", label: "Start" }, { id: "configure", label: "Configure" }]} />EmptyState
Explains why there's nothing to show and offers the next action. The danger tone is used for recoverable errors (role=alert).
No templates match your search.
The agent couldn't make a plan
Rate limit reached. Try again in a few minutes.
Usage
<EmptyState icon="🔍" title="No templates match" action={<Button>Clear filters</Button>} />
<EmptyState tone="danger" title="Couldn't plan" description={error} action={…} />Skeleton & Spinner
Skeletons mirror the shape of the content being loaded (used while the AI agent plans). Wrap in role=status with a visually hidden label. Spinner is for inline/button loading.
Usage
<div role="status"><span className="sr-only">Loading…</span><Skeleton className="h-4 w-2/3" /></div>
<Spinner label="Loading" />