Design Tokens
Date: 2026-08-16
Named values for design decisions, stored independently of any implementation. The contract between design and code — and the whole benefit comes from the naming layer, not from the values.
Design tokens are the design decisions of a system — colour, spacing, type, radius, shadow, duration — extracted into named values that every implementation references rather than hard-codes.
Storing #1d4ed8 in a JSON file achieves nothing. Storing it as colour.action.default is the entire idea, because now the name can outlive the value.
The three tiers
The mechanism that makes tokens worth having. Skip it and you have a variables file.
PRIMITIVE raw values, no meaning
blue.600 #1d4ed8
red.500 #ef4444
space.4 16px
SEMANTIC meaning, not appearance
colour.action → blue.600
colour.danger → red.500
space.inset.md → space.4
COMPONENT scoped to one thing
button.bg.default → colour.action
button.padding → space.inset.md
Each tier answers a different change:
rebrand → change PRIMITIVES only
dark mode → change SEMANTIC mappings
button tweak → change COMPONENT tokens
That’s the payoff. A rebrand touches one file of hex codes; a component never referenced a hex code in the first place.
What goes wrong without the semantic tier
The common failure is stopping at primitives and using them everywhere:
BEFORE AFTER A REBRAND
.btn { bg: blue.600 } find/replace across
.link { c: blue.600 } the whole codebase,
.focus { o: blue.600 } deciding each case
.badge { bg: blue.600 } individually
Every one of those blue.600 uses meant something different — action, navigation, focus indicator, status. They only looked identical because the brand happened to make them so, and the rebrand is where that collapses.
With semantics, focus stays focus when the brand blue changes.
Naming
The hard part, and the part that decides whether anyone uses them.
- Name by role, not appearance.
colour.dangersurvives;colour.reddoesn’t — Colour Systems - A consistent grammar.
category.role.variant.stateor similar, applied without exception. Inconsistency here is what makes a token set unbrowsable - Don’t encode the value.
space.16is fine as a primitive;padding.16pxin a component is a hard-coded value wearing a costume - Resist over-tokenising. A token for every property of every component is a second codebase with none of the tooling
Format and delivery
Tokens are stored once and transformed for each target.
tokens.json
↓ build step
├─ CSS custom properties web
├─ SCSS variables legacy web
├─ JS / TS object runtime access
├─ iOS / Android native
└─ back into the design tool
The W3C Design Tokens format is the emerging interchange standard, which matters because it’s what lets design tools and build tools agree — Design Tokens in Practice.
[CHECK: current status of the W3C Design Tokens Community Group specification before depending on it.]
What tokens can’t do
- They don’t make things consistent. They make consistency possible. A developer can still write
padding: 13px - They don’t capture composition. “This card uses medium inset spacing and a subtle shadow” is a component decision, not a token
- They rot like anything else. A token nobody references is still shipped, still documented, and still suggests it’s supported — Component Inventory
Where to draw the line
Tokenise a value when the same decision appears in more than one place and would need to change together. That’s the test.
A one-off value used in one component is not a token; it’s a value. Adding it to the system creates a maintenance obligation in exchange for nothing — Design Systems.