Design Tokens
Design tokens are the named values that define a design system's visual DNA — colors, spacing, type scale — and CSS custom properties are their natural home.
- Define what a design token is and why it exists as a distinct concept
- Distinguish primitive tokens from semantic tokens and know when to use each
- Organise a token vocabulary across color, spacing, and typography
- Explain how semantic tokens insulate components from palette changes
- Describe where tooling fits (Figma tokens, Style Dictionary) without being bound to any specific tool
Open any large codebase without a design system and run a search for #3a7bd5.
You might find it in 40 files. Some are buttons, some are borders, some are
focus rings. Now the brand team wants to shift the primary blue slightly warmer.
That is not a refactor — that is archaeology. Design
tokens exist to make sure this problem never happens.
What a design token is
A design token is a named design decision. Not a color value — a decision that a particular color carries a specific meaning in the system:
- "primary interactive color"
- "spacing step 4"
- "body text size"
The token is the name. The value is what you assign to it today. When the value changes, the name stays the same, and every component that references the name updates automatically.
Tokens are the decision layer between the design tool (Figma, Sketch) and the
codebase. When designers and developers share the same token names, they speak
the same language. A designer changing color-interactive in Figma and an
engineer updating --color-interactive in CSS are making the same change in
their respective tools.
Primitive tokens and semantic tokens
Token vocabularies have two tiers:
Primitive tokens name a value in the design palette without saying what it is for:
:root {
/* Color palette */
--color-blue-300: #93bbf8;
--color-blue-500: #3a7bd5;
--color-blue-700: #1e4fa3;
/* Spacing scale */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-4: 1rem;
--space-8: 2rem;
/* Type scale */
--font-size-sm: 0.875rem;
--font-size-md: 1rem;
--font-size-lg: 1.25rem;
--font-size-xl: 1.5rem;
--font-size-2xl: 2rem;
}Semantic tokens give purpose to the primitives. They say what a value is for, not what it is:
:root {
--color-interactive: var(--color-blue-500);
--color-interactive-hover: var(--color-blue-700);
--color-interactive-subtle: var(--color-blue-300);
--color-bg: #ffffff;
--color-text: #1a1a2e;
--color-border: #d1d5e0;
--space-component-padding: var(--space-4);
--space-section-gap: var(--space-8);
}Semantic tokens are what component code should reference:
.button {
background-color: var(--color-interactive);
padding: var(--space-component-padding) calc(var(--space-component-padding) * 2);
}
.button:hover {
background-color: var(--color-interactive-hover);
}Now if the brand blue changes from #3a7bd5 to #2a6bcc, you update one
primitive token. Every component that reads --color-interactive picks up the
new value automatically — because the semantic token still points at the same
primitive name.
The two-tier structure also makes theming clean. Dark mode only needs to redefine semantic tokens. Primitive tokens stay the same — you are not changing the palette, you are changing which palette value each semantic role uses.
[data-theme="dark"] {
--color-bg: #1a1a2e;
--color-text: #e8eaf6;
/* --color-interactive stays the same: blue works on dark too */
}A token taxonomy
A mature token set covers five domains:
| Domain | What to tokenise |
|---|---|
| Color | Palette steps (primitives) + semantic roles (background, text, interactive, border, feedback states) |
| Spacing | A numeric scale — usually 0.25rem steps or a ratio — plus named semantic spacings |
| Typography | Font families, size scale, weights, line heights |
| Radius | Border radii — none, small, medium, large, full |
| Shadow | Elevation levels — usually two or three steps |
Resist the temptation to over-engineer: start with color and spacing, add typography when you have a defined type system, and add the others only when you need them. A token set with 12 carefully chosen tokens is more usable than one with 200 tokens nobody remembers.
Organising tokens as custom properties
Primitive tokens live on :root. Semantic tokens can live there too, or in a
dedicated layer:
@layer reset, tokens, base, components, utilities;
@layer tokens {
:root {
/* Primitives */
--color-blue-500: #3a7bd5;
--color-neutral-900: #1a1a2e;
--space-unit: 0.25rem;
/* Semantics */
--color-interactive: var(--color-blue-500);
--color-text: var(--color-neutral-900);
--space-4: calc(var(--space-unit) * 4); /* 1rem */
--space-8: calc(var(--space-unit) * 8); /* 2rem */
}
}Placing tokens in their own layer means base styles and components can safely
reference them with confidence — a component rule in @layer components will
never accidentally override a token in @layer tokens.
Tooling
Tokens can be authored as JSON or YAML and compiled to CSS custom properties by a tool like Style Dictionary. This lets the same token file also generate iOS color constants, Android color resources, or a JavaScript theme object — all from one source of truth.
{
"color": {
"blue": {
"500": { "value": "#3a7bd5" }
},
"interactive": { "value": "{color.blue.500}" }
}
}Style Dictionary processes this file and outputs:
:root {
--color-blue-500: #3a7bd5;
--color-interactive: var(--color-blue-500);
}Figma's native variables feature can export tokens in the W3C Design Tokens Community Group format, which Style Dictionary (and other tools) can consume directly. This closes the loop: designers change a value in Figma, the token file is updated, CI regenerates the CSS, and the change propagates everywhere.
Hand-authored token files work perfectly well for smaller teams. You do not
need Figma integration or Style Dictionary to benefit from the token pattern —
a well-organised :root block with clear naming conventions delivers most of
the value. Add tooling when manual synchronisation between design and code
becomes a real friction point.
Where to go next
Tokens and layers give you a principled way to organise values and priorities in a stylesheet. The remaining question is how to organise the rules themselves — the class names, the selectors, the patterns developers follow when writing new styles. Next: CSS Methodology Trade-offs — a comparison of utility-first, BEM, and component-scoped CSS so you can evaluate which approach fits your team and project.
The Cascade in Production
In real codebases, the cascade becomes the cascade layer — predictable ordering of stylesheets that eliminates specificity wars without reaching for !important.
CSS Methodology Trade-offs
Utility-first, component-scoped, and BEM are different answers to the same question — how do you stop CSS from growing into an unmanageable tangle at scale?