Skip to main content
Back to module
Design Systems & Developer Handoff · Design Systems

Design tokens

Reading44mLesson 2 of 3 in this module

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:

TierPurposeExamples
Primitive (core, global)The palette and raw scales, named by what they areblue-600: #2563eb, space-3: 16px
Semantic (alias)What a value is for, pointing at a primitivecolor-primary: blue-600, color-text: gray-900
Component (optional)One component's specific usebutton-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 as color-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-button breaks 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 with var().
  • 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.

Also in this lesson: