Tags: web-dev concept

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.