Matthew Huntsberry

Field guide

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.

  1. Primitives

    292

    raw values · no intent

    --_palette-gray-11
  2. Semantic

    179

    roles · intent, no value

    --surface-1
  3. Component

    14

    divergence only

    --control-bg
Each tier resolves to the one above it. A component never reaches past the semantic layer, which is what makes a value replaceable.

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.