NVX UI · v0.5

Design system

Living style guide for NVX Stack Builder: semantic design tokens (black-first theme referencing developer.apple.com, with a light theme on the toggle) 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-raisedSelected segment / tab · bg-raised
  • --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-primaryFilled call-to-action (blue pill) · bg-primary
  • --nvx-linkLinks, active text · text-link
  • --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-accentIcon highlights · 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 windows (always dark) · bg-code-bg
  • --nvx-code-promptShell prompt in code · text-code-prompt
Usage
tsx
<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

Inter (an SF-like grotesk) for headlines and body; bold, tightly tracked display type for heroes and page titles. IBM Plex Sans Thai covers Thai glyphs (Thai headings use looser tracking and taller line-height). IBM Plex Mono is only for code, commands and small tags. The one terminal touch left is the code window: $ prompts and a blinking cursor in the hero (static under reduced motion).

  • text-display · 64pxRunnable 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 · ภาษาไทย

Link style · underline on hover

$ npm run dev

Usage
tsx
<h1 className="text-5xl font-bold tracking-tighter">…</h1>
<h2 className="text-3xl font-semibold tracking-tight">…</h2>
<p className="text-lg text-muted">…</p>
<a className="link">Learn more</a>
<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, and generous section padding (py-20 to py-24). Apple-like corners: md 10px for inputs, xl 18px for cards, 2xl 22px for large tiles; buttons and chips are pills (rounded-full).

1 · 4px
2 · 8px
3 · 12px
4 · 16px
6 · 24px
8 · 32px
12 · 48px
16 · 64px

sm

md

lg

xl

2xl

full

Elevation & motion

Black-first surfaces separate by tone and hairline borders rather than shadow: sm is none in dark, md is a soft drop on hover, lg for dialogs and toasts. Motion uses three durations and two easings, and all animation is disabled under prefers-reduced-motion.

shadow-sm
shadow-md
shadow-lg
  • --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

Button

Six variants × four sizes. One primary button per view. `loading` shows a spinner and sets aria-busy; `asChild` styles a Link; icon-only buttons need an aria-label.

sm
md
lg
asChild → /builder
Usage
tsx
<Button>Save</Button>
<Button variant="secondary" size="sm">Cancel</Button>
<Button loading>Planning…</Button>
<Button variant="ghost" size="icon" aria-label="Remove"><Trash2 /></Button>
<Button asChild><Link href="/builder">Open</Link></Button>

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 - _ .

Usage
tsx
<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

JS package manager
Usage
tsx
<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.

12 steps · Node.js

interactive

Hover me — lifts with shadow-md.

Usage
tsx
<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.

neutralprimarysuccesswarningdangerinfooutlinesize sm
Usage
tsx
<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.

Describe your app and let the agent plan it.
Usage
tsx
<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
tsx
<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 use the close button to dismiss, auto-hide after 3.5 s and paused on hover/focus.

Usage
tsx
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.

Command palette: Ctrl K / ⌘ K
Usage
tsx
<Tooltip content="Move up"><Button size="icon" aria-label="Move up"><ArrowUp /></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.

bash
nvm install --ltsnpm install rechartsnpm run dev
src/app/page.tsx
export default function Page() {
  return <h1>Hello NVX</h1>;
}
Usage
tsx
<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 a check icon. Clickable when onStepChange is set. Vertical on mobile.

Usage
tsx
<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.

Usage
tsx
<EmptyState icon={<SearchX />} 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.

Loading…
Spinner
Usage
tsx
<div role="status"><span className="sr-only">Loading…</span><Skeleton className="h-4 w-2/3" /></div>
<Spinner label="Loading" />

Icons & tool tiles

Recognisable tools, recognisable marks. Tool logos come from simple-icons (CC0 data; logos are trademarks of their owners, used only to identify the tools) and render as app-icon tiles, the way Apple Developer shows Xcode and Swift. Everything else uses lucide line icons at 1.75–2px stroke. No emoji.

nodenpmpnpmyarnnvmpythonpypiuvnextreactviteexpressfastapistreamlittypescripttailwinddockergithubactionsvitestruffai
Usage
tsx
import { ToolTile, ToolIcon } from "@/components/brand/tool-icon";
<ToolTile id="next" size="lg" />      // app-icon tile, official colour
<ToolIcon id="npm" className="text-sm" /> // inline mark, currentColor
import { Sparkles } from "lucide-react";
<Sparkles className="h-4 w-4" aria-hidden />

Brand mascot

The pink blob is the NVX logo and a living character: it floats and breathes, blinks now and then, follows the cursor with its eyes (mouse only) and squishes when hovered or clicked. Everything stops under reduced motion. Its pink (--nvx-brand-pink #fb0fab) is decorative only, used for the mascot glow and the OG card. Buttons, links and focus stay blue.

28px48px112px · interactivebrand-pink #fb0fab
Usage
tsx
import { Mascot } from "@/components/brand/mascot";
<Mascot size={28} />                 // header: float + blink only, decorative
<Mascot size={112} interactive />    // hero: + eyes follow cursor, squish on hover/click
<Mascot size={64} label="NVX" />     // role="img" with an accessible name

DevStack signature

The DevStack signature appears on every page (footer) and on the docs landing. A black terminal with a thick white outline, four tool tiles (JavaScript, curl, npm, PyPI; simple-icons marks plus text) and a prompt that types real, working commands, with the matching tile lit. Move the pointer near it: the outline lights up where you are, text glows on hover and the cursor pulses. On touch, a tap lights the edge. Static under reduced motion. HTML + inline SVG in container-query units, so it is crisp at any size.

160px · static
280px · static
440px · typing
  • js npm create vite@latest my-app
  • curl curl -fsSL https://devstack.bid/llms.txt
  • npm npx create-next-app@latest my-app
  • pypi pip install fastapi
Usage
tsx
import { TerminalSignature } from "@/components/brand/terminal-signature";
<TerminalSignature className="w-56" />                 // footer
<TerminalSignature className="w-full max-w-md" />      // docs landing
<TerminalSignature animate={false} />                  // static (shows the curl command)