Design Tokens in Practice
Date: 2026-08-16
The pipeline that carries token values from where they’re decided to every place they’re consumed. The concept is simple; the pipeline is where design systems actually break, because it’s the one part neither designers nor developers own by default.
A token pipeline transforms one source of truth into every format consumers need — CSS, native platforms, the design tool — and keeps them synchronised.
The concept is in Design Tokens. This is the plumbing.
The shape
SOURCE OF TRUTH
tokens.json (W3C format)
│
▼
TRANSFORM
(Style Dictionary or similar)
│
┌────┼────────┬─────────┐
▼ ▼ ▼ ▼
CSS JS/TS iOS back into
vars object Android the design tool
One source, many outputs, generated on every change. The moment a value is hand-copied into a second place, the pipeline has failed and drift begins.
Where the source of truth lives
The decision that shapes everything else, and there’s no universally right answer.
IN THE DESIGN TOOL
✓ designers own it directly
✓ changes start where decided
✗ needs an export step, often via a
plugin or the tool's API
✗ design tools are not version control
IN THE REPO
✓ real version control, review,
CI, history
✓ engineering workflow already exists
✗ designers need a PR to change a
colour, which they will not do
Repo-as-source with a good sync back into the design tool is the more robust choice, because tokens are code and benefit from review, history and CI. The cost is real and must be paid: if changing a token requires a designer to open a pull request, tokens will be changed by developers guessing, or not at all.
Whichever way round, one direction only. Bidirectional sync produces conflicts nobody can resolve.
The source file
{
"colour": {
"blue": {
"600": { "$value": "#1d4ed8",
"$type": "color" }
},
"action": {
"default": {
"$value": "{colour.blue.600}",
"$type": "color"
}
}
}
}{colour.blue.600} is a reference, not a copy — the semantic tier pointing at the primitive tier. That indirection is what the whole build resolves.
Output
/* generated — do not edit */
:root {
--colour-blue-600: #1d4ed8;
--colour-action-default: var(--colour-blue-600);
}Keep the reference in the output where the target supports it. CSS custom properties preserve var(), so a theme can override the primitive and the semantic follows. Flattening to hex at build time loses that and forces a rebuild per theme — Theming.
Making it hold
- Generated files are never edited. Say so in a header comment and enforce it in CI — a modified generated file should fail the build
- Lint for literals. A rule rejecting raw hex colours and pixel values in component code is what actually keeps tokens used. Without it, tokens are a suggestion
- Version the token package separately from the components, so a colour change doesn’t require a component release — Design System Versioning
- Diff visually on token changes. A one-line change to a primitive can alter every screen, and a visual regression run is the only thing that catches it
What it doesn’t solve
- Composite decisions. “A card has medium inset padding and a subtle shadow” isn’t a token, it’s a component decision. Trying to tokenise it produces a component-shaped token file — Component API Design
- Naming. The pipeline moves whatever names you chose. A badly-named token set is badly named in six formats
- Design-tool component parity. Tokens sync; components don’t. A design-tool button with a variant the code lacks is still a gap, and it’s the more common failure — Design Handoff
The realistic minimum
For a single-platform product, the full pipeline is over-engineering.
tokens.json → CSS custom properties
→ a TS type for autocomplete
That’s it, and it delivers most of the value. Add platforms and design-tool sync when there is a second consumer, not before — a five-target pipeline serving one website is maintenance with no return.