Field guide · 01
Give every value an address.
Most token systems fail the same way. A value gets a name, the name gets used everywhere, and then the value has to change. Everything below is measured from the stylesheet rendering this page: 485 tokens, 431 references between them, and 648 states you can put it in.
00
The problem is not the value
A hardcoded colour is not wrong because it is a hex. It is wrong because it has no address. When the brand changes, you cannot ask the system where that value went — you can only search for it, and search finds the ones that match the string, not the ones that meant it.
Every tier below exists to give a value an address, so that changing it later is a lookup rather than an excavation.
01
Primitives are inventory, not design
The bottom tier is a warehouse: 292 raw values — 177 private steps and 115 named palette entries. None of them decide anything. A primitive named --_palette-gray-11 tells you what it is and nothing about where it belongs.
That emptiness is deliberate. Give a primitive intent, name it --gray-for-buttons, and the layer is spent: the next component that wants that grey either misuses the name or adds a duplicate.
02
Semantic tokens carry the intent
The middle tier is where meaning lives: 179 roles like --surface-1, --text-2, --brand-solid. A role says what a value is for, never what it is. It points at a primitive, and that one hop is what lets you swap the value without touching anything that uses it.
Primitives
292
raw values · no intent
--_palette-gray-11Semantic
179
roles · intent, no value
--surface-1Component
14
divergence only
--control-bg
Across the sheet there are 431 of these references. Each one is a place a value can be re-pointed without touching the thing that consumes it.
03
Component tokens are the smallest tier, and should be
There are only 14 component-scoped tokens here — things like --control-height and --control-bg. A component tier that grows fast is a signal, not an achievement: it usually means the semantic layer is missing a role and each component is inventing its own.
The rule that keeps it small: a component token earns its place only when the component genuinely diverges from every existing role. Not when it is convenient.
04
A mode is one definition with two answers
30 tokens on this page are declared once and resolve to different values in light and dark, through light-dark(). Not two stylesheets, not a duplicated block under a media query — one declaration that already knows both answers.
The test for whether a theme is really a mode: adding it should not change the number of places a colour is declared. If dark mode made your stylesheet twice as long, it is a fork wearing a mode’s name.
05
Modes should compose, not multiply
This system has 6 independent dimensions — brand 4 · density 2 · motion 3 · radius 3 · theme 3 · type 3 — and they combine freely.
brand
mono · azure · moss · ember
re-points 10
density
comfortable · dense
re-points 18
motion
calm · expressive · default
re-points 5
radius
sharp · round · default
re-points 6
theme
light · dark · system
resolves in place
type
tight · airy · default
re-points 5
648 reachable states, written as 14 blocks of declarations.
A system that multiplies needs a variant per combination. Dense dark azure. Comfortable light moss. Every one written by hand, and the matrix collapses under itself long before it reaches this size. Here each dimension only re-points tokens, so a seventh costs one block rather than another 648.
You can confirm it: open the theme panel in the header and change any two dimensions. Nothing on this page branches on which mode is active.
06
What it costs to skip a tier
This system did not start clean. Converting it to three tiers meant rewriting 313 literal values in component CSS to semantic tokens, and deleting roughly 60 tokens nothing referenced — including letter-spacing steps that ran to -2em and would have collapsed any text that used them.
Dead tokens are the tell. They accumulate when a system has names but no chain of custody: nobody can prove a token is unused, so nobody deletes it, so the vocabulary grows faster than the design.
07
The payoff is the size of the diff
A rebrand on this system is a block of five values. Not a component sweep, not a search for hexes, not a spreadsheet reconciling Figma against production. The brand tokens get re-pointed and 431 references resolve to the new values on the next paint.
Count the size of that diff, not the number of tokens. It is the only figure that tells you whether the architecture is doing anything.