Principles / 01

Tokens, not values

A design system is a vocabulary, not a paint kit. Why every literal value in source is a bug.

Section: Foundations

The cheap part is the hex code

People reach for the words "design system" when they mean "shared CSS file." A shared CSS file is necessary, but it is the cheap part. The expensive part — the part the system is actually for — is the agreement on what a radius is called. The hex code is trivia. The name is the contract.

When two engineers, separated by months and a JIRA ticket, both reach for --radius-md, that is the system working. They have to agree on very little: that there exists a thing called md, that it sits between sm and lg, and that it is the right pick for cards. Whether that resolves to 8px or 10px is, in the long run, less important than whether they reach for the same name.

The disaster is when the name resolves to different values depending on which CSS file loaded last.

What the audit actually found

Week 1 of this sprint started with an inventory. We did not begin by writing tokens; we began by counting. The results:

  • 1,016 unique CSS custom properties across six token files.
  • 2,181 total lines of token declarations. About a third were duplicates with subtly different values.
  • 14% of box-shadow uses referenced a token; 86% were ad-hoc.
  • 8.5% of z-index uses referenced a token. 91.5% were integer literals.
  • ~10% of border-radius values were tokenized. The rest were pixel literals.

None of that is unusual — every codebase that has shipped for a year looks like this. What was unusual was the cascade-order chaos. We found a 3-way collision on --radius-md:

  • enterprise-tokens.css set it to 8px.
  • premium-b2b-tokens.css set it to 8px.
  • ypai-variables.css set it to 16px.
  • The component using it ended up with 12px, because a fourth file shadowed both.

Same name. Four possible values. The actual resolved value depended entirely on which file was last imported in global.css — a property no engineer working on a single component could realistically check. That is not a typing mistake. That is the system having no opinion about what md means.

The z-index arms race

The other tell that the system has stopped meaning anything is the integer-literal arms race. Counting unique z-index values across src, we found:

-1, 0, 1, 2, 3, 10, 50, 100, 999, 1000, 9999, 10000, 10001, 99998, 99999, 100000, 100001

Seventeen distinct values. The 99998 → 100001 cluster is a fossil record of two components losing a stacking fight: each one bumped its number by one, then by ten, then by a thousand, hoping to win the cascade. None of them fixed the actual problem (a parent position: relative creating a new stacking context). All of them made it worse for the next engineer.

Renaming the seventeen values into eight semantic layers (--ds-z-base through --ds-z-toast) does not magically fix the underlying stacking-context bugs. What it does is make those bugs nameable. "Toast is below modal" is a sentence you can argue about. "9999 is below 99999" is a sentence that has already lost.

A design system is a vocabulary

The deeper claim is this: a design system's job is not to make your site look consistent. Consistency is a byproduct. The job is to give a team a shared vocabulary for visual decisions, so that "this card looks wrong" can resolve to a specific token, and "we should add a tighter radius" can resolve to a specific argument with a specific outcome.

Vocabularies have rules. They do not include every word; they include the words worth having. Our radius scale has eight steps because eight is enough — adding a ninth would mean every PR review has to debate which step a new component lands on. Our spacing scale is on a 4-point grid because the grid is the rule that lets us add a 17th value without re-litigating the rest.

A paint kit, by contrast, has no rules. It has paint. Anyone can mix new colors. The result is what we found in the audit: a thousand colors, no agreement on which is which, and a cascade-order roulette every time the page loads.

What this commits us to

The implication is operational, not aesthetic. From this point forward in the system:

  1. No literal px values in component styles. Every spacing, radius, shadow, and z-index must reference a --ds-* token.
  2. No new tokens without a written reason. Adding --ds-radius-xs required a one-paragraph defense of why we needed something smaller than sm.
  3. Old tokens get migrated, not aliased. The --radius-md collision is being resolved by a codemod (Week 3, 499 files, 8,814 replacements), not by a compatibility shim.
  4. Lint rules guard the boundary. Magic z-index integers fail CI. Hex colors outside the token files fail CI.

The result is not that the site looks dramatically different. The site looked fine before. The result is that the next engineer who reaches for "the medium radius" gets the same value every time, and that argument nobody was having — about what 9999 means — can finally stop.