All documents

Page anatomy

Three devices build every screen: Screen, PageHeader, Panel (components/brand/devices.tsx).

Screen — the page box, in two rhythms

<Screen>          {/* a scrolling document */}
<Screen fill>     {/* a viewport application */}
paddinggap between blocksbottom
default
24px, 32px at lg
40px
80–96px
fill
24px, 32px at lg
24px
24–32px, nothing scrolls but the child

Use fill when one child owns the scroll (a table, a map, a long list). Use the default for documents, overviews and forms.

PageHeader

<PageHeader title="User management" meta={<Badge …/>} description="…" actions={…} />
  • Actions share the title's line, not the header's box. Centring them against title-plus-description drops a button into the gap between two lines of prose, pointing at neither.
  • meta is for badges that qualify the title — a count, a vintage, a scope.
  • description is capped at 76 characters wide, in secondary ink. One sentence.
  • The action box is min-w-0, not shrink-0, so it wraps on a phone instead of being cut off by the main column's overflow-x: hidden.

There is one <h1> per screen and it is this one: DM Sans 800 at text-display, leading-none, tracking-tight.

Panel

A card that holds something with its own scroll — a table, a map, a list. Zero padding, because the thing inside owns its edges; a table's cell padding plus a card's padding is two paddings, and the sticky header then pins below a strip of card instead of at the top of the box.

<Panel className="min-h-0 flex-1"> is the standard pairing inside Screen fill.

StatTile and StatRow

The loudest voice in the system: a mono-caps label, a DM Sans 800 numeral at text-stat with hero tracking, and one line of qualifying text underneath. The qualifier is required — a number with nothing saying what it counts is the thing this system exists to prevent.

Four to a desktop row, two on a phone (StatRow). Tone (positive, caution, danger) colours the numeral when it is genuinely a state; accent when it is the subject. Default is plain foreground, and most tiles should stay that way.

Metric — the composable figure

StatTile is the fixed device: label, numeral, qualifier. Metric is the same figure with every extra part optional, for when one screen needs more of it than another:

PartPropNotes
The qualifier
sub
Required. A number with nothing saying what it counts is not a fact
The change
delta
Needs a direction and a meaning, not a sign — churn going up is bad, and that is a fact about the metric, not the arithmetic
A shape
series
Sparkline, on the numeral's baseline. Twelve points or so
A target
progress
The 5px track, 0–100
The hint
info
The (?), one or two sentences
A state
tone
Only when the figure genuinely has one. Most do not

Sparkline and Delta are exported on their own, for a table cell or a line of prose.

The sparkline is hand-drawn SVG, not Recharts, and that is deliberate. It has no axes, no legend and no tooltip — it is a shape in a line of text. Mounting a chart library to draw twelve points costs tens of kilobytes and a layout pass for what forty lines of path arithmetic do exactly. Recharts (components/ui/chart.tsx) earns its place on a real chart, where the axes, the ticks and the tooltip are the work.

Callout

Two 14px accent bracket corners, top-left and bottom-right. No background, no box, and never a coloured top or left border — that is on the never list. tone="danger" swaps the marks to the danger colour for a do-not callout.

Eyebrow and Hint

Eyebrow is the mono-caps section label — a section is marked with type. Hint is one line of secondary prose capped at 76 characters, for a caveat that belongs beside the thing it qualifies.

Reading width and rhythm

Prose is capped at 76 characters. Marketing sections get 96 / 64 / 48px of vertical padding and a 1280px centred container. Product screens do not — they fill their column.

The hub

The workspace's front door: the tools as tiles, built from the same PRODUCTS list the rail's switcher uses. soon tools render as tiles that do not link. adminOnly tools appear only for an admin — except on an unconfigured deploy, where the route really is open and hiding the tile would hide that fact.