Tokens and surfaces
Tokens are the names components read. There is no second layer of aliases: a custom property
whose value contains var() is substituted where it is declared, so an alias defined at
:root resolves there and cannot be re-pointed in a subtree.
@theme { --control-font-size: var(--text-sm); /* broken: resolved at :root */ --font-sans: var( --font-family, ui-sans-serif ); /* worse: the fallback hides the failure */}The second shape is the sharper one. A bare alias renders nothing and eventually gets noticed; an alias with a fallback renders a plausible system font and nobody reports it.
The list
Section titled “The list”| Group | Tokens | Notes |
|---|---|---|
| colour — surface tier | --color-background, --color-foreground, --color-muted, --color-muted-foreground, --color-border, --color-highlight, --color-highlight-foreground |
flip per surface; --color-input and --color-ring follow --color-border |
| colour — brand tier | --color-primary, --color-secondary, --color-brand, --color-destructive, each with a -foreground |
inherit across surfaces; override only on collision |
| shape | --radius-base |
one radius, not an sm/md/lg scale |
| controls | --control-height, --control-padding-x, --control-font-size |
read by buttons, inputs and selects alike, so one declaration sizes a whole form |
| rhythm | --flow-gap |
spacing between stacked children |
| layout | --container-max, --container-padding, --sheet-width, --sheet-height |
read by Container and Sheet |
| type | --font-sans |
the theme variable is the token |
Fifteen of these are colour. The entire non-colour contract is ten names, and a brand that only changes colour touches none of them.
--color-brand is the identity colour. --color-accent is retired — removed rather than
aliased, so a stale override is inert and greppable instead of silently re-pointing the identity
colour.
Surfaces
Section titled “Surfaces”A surface is a set of colour roles swapped as a unit. A component reads --color-background and
--color-foreground and never knows which surface it is on, so it renders correctly anywhere.
[data-surface="inverse"] { --color-background: var(--gray-950); --color-foreground: var(--gray-50); --color-border: var(--gray-800); --color-highlight: var(--gray-800);}Surfaces are named for the plane they describe — base, raised, inverse, brand — never for
a role. Naming one after a role is what produced the accent collision: a token that was both a
brand colour and the obvious name for a plane, so neither meaning could be relied on.
Surfaces nest. A base surface inside an inverse section is light again, and only the roles a
surface restates change — the rest inherit from the enclosing surface.
Dark mode is not a mode. The root carries a surface, and prefers-color-scheme chooses which.
Applying a surface without touching markup. A surface is a rule, so add the selector to it:
[data-surface="brand"],[data-component="content-teaser"] { --color-background: var(--brand-500); --color-foreground: white;}Which tier a role belongs to
Section titled “Which tier a role belongs to”Decidable, not a matter of taste. A brand-owned role is restated on a surface only where that brand’s colour collides with that plane — so another brand could delete the override.
Ask: name a brand that deletes this line. primary passes — an orange that reads on both light
and dark needs no restatement. highlight never could: a pale grey hover is invisible on dark for
every brand there will be, so it is surface-owned.
Adding a token
Section titled “Adding a token”Both tests must pass.
Is it an input? Something downstream reads it — a container sets it and descendants act on it, one value drives several properties, or the component decides how to apply it. A value only ever set on the element that uses it is a plain property.
Does it compete with Tailwind? Add a token only for something Tailwind does not already name.
A display-xl / heading-lg scale sits in the same space as text-4xl, so every author has to
decide which vocabulary to reach for. If a developer could be unsure which to use, do not add it.
And a brand has to have actually needed it.

