Dashboard

Styleguide

The reference and the regression test. Every token, control and pattern the product is allowed to use.

§01

Colour

Colour is semantic, never decorative: one meaning per hue, and a fifth meaning needs a fifth hue added to globals.css rather than a raw Tailwind palette colour picked at a call site. Each hue has two fill weights and picking the wrong one is the bug — -bg is chip weight (a 20px pill), -wash is surface weight (a fill behind text). Never fake a wash with a chip fill at an alpha.

Surfaces
paper
the page
card
a surface on it
sunk
a well inside a card
line
a border
hairline
a divider inside a card
chip
a quiet status
chip-firm
a decided one
Ink
ink
body
ink-2
prose
ink-3
captions
ink-4
labels
Accents
brand
identity, and anything generated
attention
waiting on a human
positive
it actually happened
danger
it was refused
neutralcommittedthe two chips with no hue to spend — quiet, and irreversible
Notice
Two records need a decision before Friday.
An alert states the fact, the number and the one action.
Brand
AnchorEd
Heading accent

Not a status — the warm serif tone for hero and greeting copy.

§02

Type

Three faces, one job each. Zodiak for headings — the page title is bold italic in the heading accent, sampled from Figma; card headings (h2–h3, 600) haven't been sampled yet and still take the base layer's plain default — Satoshi for interface, mono for data — every number, ID and timestamp, always tabular via .money. Labels use .eyebrow.

Headings

Page title, 40/40

Card heading, 15/600 — unconfirmed

Interface

Body copy is 13px on a 1.6 line height. Rows are 13px/600 with an 11px mono sub-line naming the record they hang off.

Data
$284,000
INV-1043 · 8 Sep 2026
Eyebrow
Column header
§03

Controls

One ink-filled primary action per card — the brand hue is identity, not a button fill. Chips are mono 10.5px on a tinted fill with no border, and they come from Badge: never hand-mix inline-flex … font-mono text-[10.5px]. Every control gets its colour transition and its pointer cursor from the base layer, so a bare transition-colors already complies.

Buttons
Sizes
Outline on
Fields
Menu
Right-click a table row for the same items — one list, two trees.
Dialog
Tooltip
§04

Surfaces

A card takes a 1px line ring and no shadow. The one exception is .doc-sheet, for a document a reader is asked to sign — elevation and radius are theme, so the next surface that wants to be paper uses that rather than spelling its own shadow. Amounts right-align and never wrap. Empty states are dashed boxes that say what creates the record.

Fact strip
Received
$41,000
3 payments
Outstanding
$9,500
2 overdue
Open
12
across 4 owners
Last activity
2 days ago
by Ada
Panel

Recent

4
Halcyon Freight
Northwind Labs
Bellweather Co

Nothing yet

A rail panel says this instead of a dashed box.

Table
RecordOwnerStatusValue
HF
Halcyon Freight
Logistics · Brisbane
Ada Lovelacepaid$41,000
NL
Northwind Labs
Research · Perth
Grace Hopperawaiting reply$9,500
Empty state
Nothing here yet. A record appears once somebody creates one.
Loading
Blooms
sheet-bloom
a menu sheet or a hero; the strong one
paper-bloom bloom-pair
one circle per colour, for a surface one screen tall
paper-bloom
four circles staggered down a long document, sized by --bloom-size; a surface carrying imagery paints its own background
§05

Proportion

A quantity is a bar, and the bar arrives from zero — a bar simply “there” on load reads as a rule under the numbers. The fill comes from lib/accents (POSITIVE_BAR, NEUTRAL_BAR), never a raw colour, and the number is always stated beside it, which is why the bar itself is aria-hidden. “How far along” has one definition: lib/progress gives the share, whether it is complete, and which fill a completion bar takes. Positive means it happened, so the bar only turns positive at the end.

Completion
Collected$41,000 of $50,500
Judged12 of 12
Quantity
Capacity34%
§06

Motion

One curve for everything: --ease-soft at 150ms, which is also Tailwind's default — so don't spell duration-150 ease-soft, spell the exceptions (120ms for a drop-target highlight, 300ms for a marker travelling, 700ms for a bar filling). Content that mounts once uses an animate-*-in token; anything with a data-state and a matching exit uses the tw-animate-css utilities instead. Reduced motion zeroes each keyframe's movement variable rather than switching animation off, so spinners keep spinning.

These replay on demand, which is what makes this section a test: press Replay and either they move or the theme is broken.

outcome-in
$9,000 received — invoice INV-1043 settled.
origin-in
Rewrite this section
reveal-in

The field group that replaced its sibling, unfolding downward.

Sliding marker
Collapse