Code of the Day
AdvancedCSS Architecture

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.

Web FoundationsAdvanced7 min read
Recommended first
By the end of this lesson you will be able to:
  • 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. 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:

DomainWhat to tokenise
ColorPalette steps (primitives) + semantic roles (background, text, interactive, border, feedback states)
SpacingA numeric scale — usually 0.25rem steps or a ratio — plus named semantic spacings
TypographyFont families, size scale, weights, line heights
RadiusBorder radii — none, small, medium, large, full
ShadowElevation 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.

Finished reading? Mark it complete to track your progress.

On this page