All articles
Design Tokens

What Token Drift Is, and How to Design It Out

JUL 5, 2026 · 6 min read · Subin

Token drift never announces itself. It starts with a single hardcoded value shipped under deadline pressure, and a year later the product has four slightly different grays for the same surface, with nobody able to say which one is canonical.

It arrives one shortcut at a time

One hex typed straight into a component. Then a spacing tweak that skips the scale. Then a shadow that lives in exactly one file. Each shortcut is reasonable on its own and invisible in review. Together they are drift, and drift compounds silently until a redesign forces you to count the damage.

You can feel it in the symptoms: colours that are almost but not quite the same, spacing that looks off by a pixel you cannot locate, a dark mode that is right on most screens and wrong on a few.

Card
#2b2d31
Modal
#2b2d31
Header
#2b2d31
Sidebar
#2b2d31
1 value across every surface. No drift: change the token once and all four move together.
Ship a few tweaks in each mode. Hardcoded values scatter into different grays; a semantic token keeps every surface in lockstep.

Drift is an architecture problem, not a discipline problem

Telling engineers to "just use the tokens" does not work, because drift is rarely carelessness. It is what happens when the token system cannot express what a screen actually needs. If there is no token for a hovered destructive action, someone will invent a value, and that invention is drift.

So the fix is not more willpower. It is a layered architecture where the right token always exists and always resolves to one place.

Primitive
What values exist
carbon-900 = #0E0F12, ember-500 = #FF7A00, space-4 = 16px. Raw, named, opinion-free.
↓ referenced by
Semantic
What they mean
--surface = carbon-900, --accent = ember-500, --danger = scarlet-500. Intent, not hex.
↓ referenced by
Component
Where they apply
button-bg = --accent, card-surface = --surface. Each layer only points at the one below it.
Three layers, each referencing only the one below. A rebrand becomes a single primitive swap, and drift has nowhere to hide.

The three layers

Primitives define what values exist: the raw ramps and scales, named but opinion-free. Semantic tokens define what those values mean: surface, accent, danger, the intent behind a choice. Component tokens define where they apply: button background points at accent, card surface points at surface.

When each layer only references the layer below, a theme change is a swap at the primitive level that ripples upward automatically. Nothing hardcodes a value, so nothing can drift away from it.

Missing tokens cause more drift than careless engineersEvery state a screen needs but the system cannot name is a future hardcoded value. Before you blame the team, look for the gap in the token set. Fill the gap and the shortcut disappears with it.

Automate the boundary

Architecture removes the reason to drift; automation removes the opportunity. Sync tokens from the source of truth into code on every change, generated and versioned like any other dependency. Then lint component styles for raw values so a stray hex fails the pull request instead of surfacing in an audit two quarters later.

Once that pipeline exists, drift stops being a recurring cleanup project and becomes a build failure you fix in minutes. That is the difference between a system that erodes and one that compounds.

A drift-proof pipeline

  1. One source of truth for values (Figma variables or a tokens file), never a designer's memory.
  2. Generate platform tokens from it automatically on publish.
  3. Reference only semantic and component tokens in code, never raw values.
  4. Lint for hardcoded hex, px, and shadows so drift is caught in review.
  5. Version and review token changes exactly like code changes.
Discipline fights drift and loses. Architecture makes drift impossible to introduce.On design tokens

Catch drift where it is born

Drift is cheapest to fix at the moment it is typed and most expensive to fix once it has shipped and spread. That argues for moving the catch as early as possible: a lint rule that fails a raw hex in the editor and the pull request, before the value ever reaches a screen. The earlier the guardrail, the smaller the cleanup.

This is also what makes the fix durable. Architecture removes the reason to drift, but a lint removes the opportunity, and the two together turn a recurring cleanup project into a problem you simply stop being able to create.

Drift is not a moral failing on the team. It is a structural gap in the system. Close the gap with layers and a pipeline, and the problem stops being something you clean up and becomes something you cannot create.