Design Tokens

The variable layer every component reads from: how tokens are named and layered, how to consume them, and how to change one without breaking the system.

What tokens are for

A token is a named design decision. Components reference the name, never the raw value.

That indirection is the point. Change the token and every component that uses it changes with it. Hardcode the value and the change becomes a search-and-replace across the codebase, which is where drift starts.

Layers

Tokens are worth splitting into layers so that intent survives a redesign:

LayerHoldsExample
PrimitiveRaw values with no meaning attachedA blue, a spacing step, a radius
SemanticRoles that point at primitivesBody text colour, accent, surface, border
ComponentOverrides scoped to one componentCard padding, button height

Components consume the semantic layer. Reaching past it into primitives couples a component to a value rather than to a decision.

This system exposes its tokens as CSS custom properties on :root, with semantic names such as --ink, --muted, --surface, --border and --accent for colour, --font-display and --font-body for type, and --radius-card and --radius-pill for shape.

Naming

  • Name by role, not by appearance. --accent survives a rebrand, --blue does not.
  • Keep the order consistent: category, role, then variant. --accent-hover reads the same way as --accent-contrast.
  • Use the same words everywhere. Pick surface or background and never mix them.
  • Do not encode a number that will go stale. --gray-400 invites a --gray-450.

Using tokens

CSS
1.card { 2 background: var(--surface); 3 color: var(--ink); 4 border: 1px solid var(--border); 5 border-radius: var(--radius-card); 6} 7 8.card__meta { 9 color: var(--muted); 10}

Rules:

  • No hex values, font stacks or hardcoded radii inside a component stylesheet.
  • Give var() a fallback only where the token may genuinely be absent. A fallback everywhere hides a missing token instead of surfacing it.
  • Theme by reassigning tokens in a scope, not by overriding component rules:
CSS
1[data-theme='dark'] { 2 --ink: #f5f5f5; 3 --surface: #161616; 4 --border: #2c2c2c; 5}

Changing a token

A token change is a global change. Treat it as one.

  1. Confirm the change belongs at the token layer. A one-off need is a component decision, not a system one.
  2. Check contrast for every pairing the token participates in. See the accessibility guidelines for the ratios.
  3. Review the component previews. The catalog is the fastest way to see the blast radius.
  4. Record the change so consumers know why their page moved.

Adding a token

Add one when the same value appears in three or more places and represents a real decision. Before adding, check that an existing token does not already cover it. Two tokens with the same value and overlapping meaning are worse than none, since neither can be changed with confidence.

Anti-patterns

  • A token used in exactly one place. That is a local variable wearing a costume.
  • A token whose name describes its current value. It will be wrong after the next design change.
  • Component CSS that reads primitives directly, bypassing the semantic layer.
  • Two names for the same concept, kept because renaming felt risky.