v0.1.0The building blocks behind Unthought's internal tools. Copy a component in with `npx unthought-ui add <name>` — it lands in your repo as source you own, not a dependency you upgrade.
Lifted from the Unthought guide design system so tools, guides and artifacts read as one system. Every colour flips with the theme.
--bg--paper--wash--wash-deep--ink--body--muted--line--accent--accent-soft--cyan--success--warn--dangerField wires up id, aria-describedby and the invalid state for you — the plumbing people skip when hand-rolling forms.
Lowercase, kebab-case. Becomes the subdomain.
That address isn't on the allowlist.
<Field label="Email" error="That address isn't on the allowlist.">
<Input type="email" defaultValue="[email protected]" />
</Field><Badge tone="success" dot>Active</Badge>Danger and warn use role=alert so they interrupt; info and success are read in document order.
Revenue dashboard
The wrapper scrolls horizontally on its own, so a wide table never makes the page body scroll sideways.
| Client | Plan | Status | MRR |
|---|---|---|---|
| Zingler Strength | Pro | active | $249 |
| Kavanagh Sports | Starter | active | $99 |
| LD Landscaping | Pro | past due | $249 |
| Collegiate Cleaners | Starter | churned | $0 |
No tools yet
Scaffold one from the template and it'll show up here.
Base UI underneath — the hidden native input, focus handling and indeterminate state are tedious to hand-roll and easy to get subtly wrong.
Records who changed what, on every mutation.
Turning this off would expose the tool publicly.
Inherits id, aria-describedby and the invalid state from Field, exactly like Input does.
Dev login only works outside production.
A select you can type in. Reach for it once a list is long enough that scanning is slower than typing — roughly a dozen options. Matching covers hidden keywords, so “pacific” finds Los Angeles.
Type to filter — try “pacific” or “EST”, which match on keywords, not the label.
Focus trap, scroll lock, Escape and return-focus come from Base UI. Getting those wrong is what makes an app unusable by keyboard.
Errors don't auto-dismiss — if something failed, you need to still be able to read it after looking away.
const toast = useToast();
toast.success("Saved", "Changes are live.");main, healthy for 6 days.51–75 of 412
Optional centred label for the 'or' between two paths.
Sort, search, paginate and export in one component — what most internal-tool screens actually are. CSV export writes every filtered row, not just the visible page, and neutralises spreadsheet formula injection on the way out.
| Actions | |||||
|---|---|---|---|---|---|
| Zingler | revenue-dashboard | live | 2026-08-04 | $249 | |
| Zingler | crm | live | 2026-07-28 | — | |
| Kavanagh | website | live | 2026-07-30 | $99 | |
| Unthought | ads-dashboard | building | 2026-08-05 | — | |
| LD Landscaping | sms-reminders | live | 2026-07-19 | $149 |
1–5 of 8
Sidebar + responsive drawer + sticky header, with active-route highlighting. Not previewed inline because it owns the whole viewport — it's what wraps every page in a tool.
Renders a fixed 15rem sidebar above lg, and a dismissible drawer below it that closes on navigation.
<AppShell
brand={<Logo />}
nav={[
{ href: "/", label: "Overview", icon: Home },
{ href: "/clients", label: "Clients", icon: Users, badge: <Badge>4</Badge> },
]}
footer={<UserMenu />}
>
{children}
</AppShell>