One button, one overlay, one row.
Everything on this page is imported from @/components/kit. Tailwind’s stock palette is reset to initial, so text-gray-400 and rounded-2xl do not compile. That is the enforcement mechanism.
Tokens
Colour is semantic, never literal. Interaction states are alpha so a row reads identically on white and on the sidebar.
surfacePage, cards, overlays#ffffffsurface-sunkenSidebar, rails#fafafasurface-insetFilled inputs, wells#f3f4f6hoverRow hoverrgb(0 0 0 / 0.04)activeRow selectedrgb(0 0 0 / 0.06)textPrimary copy — 19.2:1rgb(15 15 16)mutedIdle labels — 6.20:1rgb(0 0 0 / 0.62)subtlePlaceholders, counts — 4.76:1rgb(0 0 0 / 0.55)lineDividersrgb(0 0 0 / 0.075)line-strongControl bordersrgb(0 0 0 / 0.16)accentFocus, brand moments#5200ffsuccessHealthy, settled#059669warningDegraded, throttled#b45309dangerFailed, destructive#dc2626Type
Base is 14px, not 16px — this is an instrument, not an article. Nine steps; anything between them is drift.
display44 / 48Requests routed to anthropic/claudeMarketing headlinetext-3xl32 / 40Requests routed to anthropic/claudePage titletext-2xl24 / 32Requests routed to anthropic/claudeSection titletext-xl20 / 28Requests routed to anthropic/claudeCard group titletext-lg16 / 24Requests routed to anthropic/claudeDialog titletext-base14 / 20Requests routed to anthropic/claudeDefault UI texttext-sm13 / 18Requests routed to anthropic/claudeDense metadatatext-xs12 / 16Requests routed to anthropic/claudeCounts, captionstext-label10 / 14Requests routed to anthropic/claudeUppercase group labelShape & elevation
Four radii and three shadows. Radius encodes scale: the bigger the surface, the rounder the corner.
chip6pxcontrol8pxcard12pxoverlay16pxraisedpopovermodalFields
Form inputs are bordered on white. Search is filled and borderless — it is chrome, not data entry.
Composer
The thread prompt. One per surface. 800px column, 24px radius, 18px field, circular send.
Badges & loading
A skeleton mirrors the element it replaces. A spinner means we are waiting on one thing; a skeleton means we are waiting on a list.
Skeleton rows
No requests yet
Point an OpenAI-compatible client at the gateway and the first receipt lands here.
Overlays
Three floating surfaces, three z-bands, one scrim. Tooltip labels, popover offers, dialog interrupts.
--z-sticky10--z-dropdown100--z-scrim200--z-modal210--z-popover300--z-tooltip400--z-toast500Surfaces
Cards never nest. Panels own their own scroll — the page never scrolls behind a fixed header.
Routing
HealthyBudgets
HoldReceipts
| Model | Provider | p50 | Cost / 1M | Status |
|---|---|---|---|---|
| claude-sonnet-4.5 | Anthropic | 740 ms | $3.00 | Healthy |
| gpt-5 | OpenAI | 910 ms | $2.50 | Healthy |
| veo-3 | fal.ai | 38 s | $0.40 / s | Degraded |
Outlawed
Each rule below is a pattern measured in PublicAI001. The counts are why the rule exists.
| Never | Measured in PublicAI001 | Instead |
|---|---|---|
| A second Button | 5 systems in PublicAI — bg-primary, bg-gray-900, #0D99FF, #ffffff+#d1d5db, CreateButton | Button with variant + size. Add a variant, never a component. |
| fixed inset-0 overlays | 6 dialog stacks, z-index from 80 to 100060, five different scrims | Dialog. Scrim is bg-scrim, z comes from --z-scrim / --z-modal. |
| Raw hex in a component | 653 unique hex values across 405 files | A semantic token. If it is missing, add it to globals.css. |
| rounded-[10px] and inline borderRadius | 115 + ~74 occurrences fighting rounded-lg and rounded-xl | chip 6 · control 8 · card 12 · overlay 16. Four values, no others. |
| onMouseEnter → style.backgroundColor | 30 files paint their own hover in JavaScript | hover: utilities. Hover is CSS, always. |
| Ad-hoc Loader2 / animate-pulse | 10 files import the spinner; hundreds inline Loader2 | Spinner and Skeleton. |
| A local search input class | listSearchInputClass copied into 8+ panels, plus 2 dead SearchInput files | SearchField. |
| A hand-rolled theme object | 7 THEMES / COLORS consts, plus dark: in only 15% of files | Tokens in :root. Dark mode will be one block, edited once. |
Adding to the kit is encouraged. Forking it is not. If a surface needs something that is not here, extend components/kit and update this page in the same commit — the reference and the code ship together or the kit stops being true.