Define primitive and semantic tokens for colour, type and spacing, and apply them with CSS custom properties.
Why it matters
Design tokens are the smallest, most reused decisions in a product: the exact blue of the primary button, the body text size, the gap between a label and its input. When those decisions live as hard-coded values scattered across hundreds of files and Figma layers, changing one (a rebrand, a dark mode, an accessibility fix) means hunting down every copy. Design tokens give each decision a name and a single source, so designers and developers use the same value and change it in one place.
The core idea: name the decision, not the value
A token is a name-value pair: color-primary = #2563eb. The name is what people use. The value can change without anyone editing the places that use it.
Most systems use two (sometimes three) tiers:
| Tier | Purpose | Examples |
|---|---|---|
| Primitive (core, global) | The palette and raw scales, named by what they are | blue-600: #2563eb, space-3: 16px |
| Semantic (alias) | What a value is for, pointing at a primitive | color-primary: blue-600, color-text: gray-900 |
| Component (optional) | One component's specific use | button-padding-x: space-4 |
Designers and developers should mostly use semantic tokens. A button uses color-primary, not blue-600. Then a dark theme or a rebrand only changes which primitive each semantic token points at.
Tokens in Figma and in code
In Figma (free plan included), variables store tokens: colour, number and string variables, organised into collections, with modes for themes such as light and dark. Styles (colour, text, effect) are an older, still common way to share values. Figma's free plan has some limits on modes, so check the current plan details if you need many themes.
In code, the simplest form of tokens is CSS custom properties (CSS variables):
:root {
--blue-600: #2563eb; /* primitive */
--color-primary: var(--blue-600); /* semantic points at primitive */
--space-3: 16px;
}
.button {
background: var(--color-primary);
padding: var(--space-2) var(--space-4);
}
A custom property is declared with two dashes, usually on :root (the html element) so it is available everywhere, and read with var(). Custom properties cascade and can be overridden, which is how themes work:
:root[data-theme="dark"] {
--color-text: #f9fafb;
--color-surface: #111827;
}
Many teams keep tokens in a JSON file and use a build tool (Style Dictionary is a popular free one) to generate CSS, iOS and Android formats from the same source. The W3C Design Tokens Community Group is working on a standard JSON format so tools can share tokens.
Worked example: a small token set
Colour primitives blue-600 #2563eb blue-700 #1d4ed8 gray-900 #111827 gray-100 #f3f4f6 white #ffffff
Colour semantic color-primary -> blue-600 color-primary-hover -> blue-700
color-on-primary -> white color-text -> gray-900
color-surface -> white color-background -> gray-100
Type font-size-body 16px font-size-heading 28px
Spacing (4/8 base) space-1 4px space-2 8px space-3 16px space-4 24px
Radius radius-md 12px
Note the spacing tokens are named by step, not by pixel value (space-3, not space-16), so the scale can be tuned later without names becoming lies. Check that each semantic colour pairing meets contrast requirements: white on #2563eb passes WCAG AA for normal text (about 5.2:1).
Naming tips
- Use a consistent pattern:
category-role-variant-state, such ascolor-primary-hover. - Name by purpose for semantic tokens (
color-danger), never by appearance (color-red), so a theme can change the appearance. - Keep the set small. Every token is a decision someone must understand.
- Document which pairs are safe together (text on surface, on-primary on primary).
Common mistakes
- Using primitives directly in components. Themes then need every component changed.
- Appearance names for semantic tokens.
color-blue-buttonbreaks the moment the button turns green. - Tokens only in Figma or only in code. They must match; one source should generate the other, or be checked regularly.
- Hundreds of one-off tokens. If a value is used once, it probably does not need a token.
- Ignoring contrast when defining colour pairs.
Quick recap
- Tokens name design decisions so they are used and changed from one source.
- Primitive tokens hold raw values; semantic tokens say what a value is for and point at primitives.
- In Figma, use variables (with modes for themes); in code, CSS custom properties on
:root, read withvar(). - Name by purpose, keep the set small, and keep design and code in sync.
Try it
On the Do tab you will replace hard-coded values in a small page with colour, type and spacing tokens written as CSS custom properties.