Unify
Every component, live, with the code to use it. They follow the theme in the header and the accent of the app they run in.
Status is coloured text on a tint — never a solid fill.
<Badge variant="success">Live</Badge>
<Badge variant="warning">Private beta</Badge>
<Badge>Draft</Badge>Raised surface, hairline border, 14px radius, no shadow. `interactive` for cards that are links.
<Card>
<CardHeader>
<CardTitle>October spending</CardTitle>
<CardDescription>42 receipts from 9 stores.</CardDescription>
</CardHeader>
<CardContent>…</CardContent>
<CardFooter><Button size="sm" variant="secondary">View receipts</Button></CardFooter>
</Card>Field wires the label, hint and error to the control for assistive tech. Fields get an accent border and a soft ring on focus.
<Field label="Email" hint="We never share it." error={errors.email}>
{(props) => <Input type="email" name="email" {...props} />}
</Field>
<Select defaultValue="eur">
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="eur">Euro (EUR)</SelectItem>
</SelectContent>
</Select>Checked is ink. A switch applies at once; a checkbox is part of a form that is saved.
<Label className="flex items-center gap-2 font-normal"><Checkbox /> Email me</Label>
<RadioGroup defaultValue="monday">
<Label className="flex items-center gap-2"><RadioGroupItem value="monday" /> Monday</Label>
</RadioGroup>
<Switch checked={on} onCheckedChange={setOn} />A few mutually exclusive options, inline. ThemeSwitcher and ThemeToggle are built on it.
<SegmentedControl value={range} onValueChange={setRange}
options={[{ value: "day", label: "Day" }, { value: "week", label: "Week" }]} />
<ThemeSwitcher /> {/* settings */}
<ThemeToggle /> {/* top bar */}Views of the same object. For navigation between pages, use links.
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="items">Items</TabsTrigger>
</TabsList>
<TabsContent value="overview">…</TabsContent>
</Tabs>Glass, 20px radius, the modal shadow. Sheets slide from an edge — mobile navigation and side panels.
<Dialog>
<DialogTrigger asChild><Button variant="secondary">Delete</Button></DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete this receipt?</DialogTitle>
<DialogDescription>This cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild><Button variant="secondary">Cancel</Button></DialogClose>
<Button variant="danger" onClick={remove}>Delete receipt</Button>
</DialogFooter>
</DialogContent>
</Dialog>Mount <Toaster /> once in the root layout; call toast() anywhere on the client.
import { toast } from "@nashyvan/unify";
toast.success("Settings saved");
toast.error("Could not reach the server", { action: { label: "Retry", onClick: retry } });Mono uppercase heads, 44px rows, a quiet row hover. Wrap in a Card for the border; it scrolls inside itself on phones.
| Store | Date | Total |
|---|---|---|
| Rewe | 12 Oct 2026 | €34.20 |
| dm | 11 Oct 2026 | €12.85 |
| Lidl | 9 Oct 2026 | €58.10 |
<Card className="overflow-hidden">
<Table>
<TableHeader><TableRow><TableHead>Store</TableHead>…</TableRow></TableHeader>
<TableBody><TableRow><TableCell>Rewe</TableCell>…</TableRow></TableBody>
</Table>
</Card>Reading receipts is paused
Backup finished
Storage almost full
The last deploy failed
<Alert variant="warning" title="Storage almost full">92% of 50 GB used.</Alert>
<Alert variant="danger" title="The last deploy failed" action={<Button size="sm">View log</Button>} />Icon, one plain sentence, one action — never a blank panel.
No receipts yet
<EmptyState icon={<Inbox />} title="No receipts yet"
description="Photograph a receipt and it shows up here."
action={<Button size="sm"><Plus /> Scan a receipt</Button>} /><Skeleton className="h-3 w-1/3" />
<Spinner />
<Progress value={72} label="Upload" />
<Progress value={92} tone="warning" label="Storage" /><Avatar name={user.name} />
<AppIcon {...ICON} size={40} />
<StatusDot status="up" pulse label="Operational" />
<Kbd>⌘</Kbd><Kbd>K</Kbd>One number per tile, tabular figures. A data list is label → value rows: detail panels and /system.
<MetricTile label="Uptime 24h" value="99.98%" caption="1 incident" href="/monitoring" />
<DataList title="App" rows={[{ label: "Version", value: "2026-10-12" }]} />Every page opens with a PageHeader: optional back link or eyebrow, the title, a description, actions on the right.
Everything you bought, item by item.
<PageHeader title="Receipts" description="Everything you bought, item by item."
back={{ href: "/settings", label: "Settings" }}
actions={<Button><Plus /> New receipt</Button>} />pnpm add @nashyvan/unify<CodeBlock title="Terminal" code="pnpm add @nashyvan/unify" />Sidebar on desktop, a sheet behind the menu button on phones, a sticky glass top bar, a 1152px content column. Sign in to see it in use.
<AppShell
brand={<AppLockup {...ICON} name="Spentry" />}
brandHref="/dashboard"
nav={[
{ items: [{ href: "/dashboard", label: "Dashboard", icon: <LayoutDashboard /> }] },
{ label: "Account", items: [{ href: "/settings", label: "Settings", icon: <Settings /> }] },
]}
actions={<><ThemeToggle /><UserMenu /></>}
>
{children}
</AppShell>