Design tokens are a contract, not a palette

2 min read

Most token files I open look like a paint catalogue: forty greys, a dozen blues and names like grey-650. They describe what exists. They say nothing about how it should be used, so every new screen reopens the same questions.

I now treat tokens as a contract. Each one is a promise: if you use this name, you get the right value on every surface, today and after the next redesign.

Three layers, one direction

The structure that keeps working for me has three layers, and references only ever point down:

  • Primitives hold raw values: a grey ramp, one accent, a type scale.
  • Semantic tokens describe a role: text, muted text, surface, line, accent.
  • Component tokens exist only when a component truly needs its own decision.
Diagram: six primitive swatches feed three role tokens, which feed two components.
Primitives feed roles; components read roles, never primitives. Source: W3C Design Tokens Community Group

Names describe intent, not appearance

--ink-muted survives a palette change. --grey-650 does not. When a name mentions a colour, the first palette change turns it into a lie.

/* primitives */
:root {
  --grey-50: #e2e8e5;
  --grey-900: #131816;
  --orange-600: #a8361a;

  /* roles: the only names components use */
  --bg: var(--grey-50);
  --ink: var(--grey-900);
  --accent: var(--orange-600);
}

/* components read roles, never primitives */
.quote {
  background: var(--bg);
  color: var(--ink);
  border: 1px solid var(--ink);
}

What the contract buys you

A rebrand becomes a new set of primitive values instead of a second design. Reviews get shorter, because the question is no longer “which grey?” but “which role?”.

If a value needs an explanation every time it is used, it is not a token yet. It is a decision nobody has made.

Start small: five or six roles cover most of a page. Add a token when two screens disagree, not before.

Building something and need a designer who can also ship it? Get in touch →