Skip to content
Design systems & tokensLesson 3 of 4Beginner16 min

Design tokens

Named design decisions — color, space, type, radius, motion — stored once, referenced everywhere. Inspect Motif's own tokens and the three-tier model.

Kept on this device only.

In one line: a design token is a named design decision — color.action, space.4, radius.md — stored in one place and referenced everywhere, so changing a value once changes it consistently across the whole product.

What it is

A token is a name bound to a value. Instead of writing #3b82f6 in forty places, you write --color-action once and reference the name. The name carries intent ("this is the action color"); the value is an implementation detail you can swap.

Tokens cover every category of visual decision: color, spacing, typography, radius, border width, shadow, and motion (durations and easings). Together they are the vocabulary the rest of the system speaks.

Why it matters

Without tokens, a rebrand is a find-and-replace across the codebase that you can never fully trust. Dark mode means duplicating every rule. A "make the spacing a touch tighter" request becomes a week of hunting magic numbers.

With tokens, those become one-line edits. The value lives in exactly one place, every component reads through the name, and a theme is just a different set of values bound to the same names.

See it

Live demo
Tweak it3
The design system inspecting itself. Switch token groups to see color, space, radius, type, and motion.

This is Motif's own token set inspecting itself — the design system rendering a live view of its primitives. Switch groups to see the color ramps, the spacing scale, the radius steps, the type scale, and the motion durations and easings. Every value is shown as text next to its sample, so meaning never depends on color alone.

How it works

Mature systems layer tokens in three tiers. Each tier references the one below it, never the one above.

  1. Primitive tokens are raw values with no meaning: blue-50 = oklch(0.62 0.19 255), space-4 = 1rem. They are the palette, not the decision.
  2. Semantic tokens alias primitives by role: color.action = blue-50, space.inset = space-4. This is where intent lives. Name them by what they are forcolor.action, never color.blue — because an appearance name breaks the moment the theme changes.
  3. Component tokens (optional) pin a specific component to semantic tokens: button.bg = color.action. Components read only these, so a component file never touches a primitive.

A theme — dark mode, high-contrast, a sub-brand — re-binds only the semantic tier. The primitives still exist and the component layer never changes; you simply point color.surface at a dark primitive instead of a light one.

Build it

The three tiers are just custom properties referencing custom properties. The discipline is in the naming, not the syntax.

CSS
CSS — primitive → semantic → component, with a dark theme
/* Tier 1 — primitives: raw values, no meaning. OKLCH keeps ramps even. */
:root {
  --blue-50: oklch(0.62 0.19 255);
  --blue-70: oklch(0.5 0.18 255);
  --gray-05: oklch(0.98 0.005 255);
  --gray-90: oklch(0.22 0.01 255);
  --space-4: 1rem;
  --radius-md: 8px;
}
 
/* Tier 2 — semantics: alias primitives by ROLE. Themes touch only this tier. */
:root {
  --color-action: var(--blue-50);
  --color-action-hover: var(--blue-70);
  --color-surface: var(--gray-05);
  --color-text: var(--gray-90);
  --space-inset: var(--space-4);
  --radius-control: var(--radius-md);
}
 
/* Tier 3 — component tokens: the only names a component is allowed to read. */
:root {
  --button-bg: var(--color-action);
  --button-bg-hover: var(--color-action-hover);
  --button-radius: var(--radius-control);
  --button-padding: var(--space-inset);
}
 
/* A theme re-binds the SEMANTIC tier only — primitives and components untouched. */
@media (prefers-color-scheme: dark) {
  :root {
    --color-surface: var(--gray-90);
    --color-text: var(--gray-05);
    /* --color-action stays: the accent reads well on dark too. */
  }
}
 
.btn {
  background: var(--button-bg);
  color: #fff;
  border-radius: var(--button-radius);
  padding: var(--button-padding);
}
.btn:hover {
  background: var(--button-bg-hover);
}

Make it yours

Use the controls beside the demo above to change token group, show css variable names, and compact density — each change updates the example live.

Experiment in the playground
  • Audit any token set by counting tiers: lots of component tokens but few semantic ones means the semantic layer is under-built, and theming will hurt.
  • Name semantic tokens by role (color.danger, space.inset), never by appearance (color.red, space.16). Appearance names are lies waiting to happen.
  • Author color ramps in OKLCH, not hex — equal numeric steps produce equal perceived steps, so your scale stays even and your contrast math stays honest.

Reproduce it with an LLM

Reproduce it with an LLM

You are a design-systems engineer. Generate a design-token set for a fictional brand called Lumen, as a single JSON object in the W3C Design Tokens format (each token has a `$value` and `$type`). Include: a primitive tier (an 11-step neutral ramp plus a brand accent ramp using OKLCH color values, a spacing scale of 6 steps, a radius scale of 4 steps, a type scale of 6 steps, and 3 motion durations + 3 easing curves) and a semantic tier that aliases primitives by role (`color.action`, `color.surface`, `color.text.primary`, `space.inset`, `radius.control`). Name semantic tokens by role, never by appearance. Add a short comment block (outside the JSON) explaining how a dark theme would re-alias only the semantic tier. Return the JSON, then the note.

Pitfalls & accessibility

  • Skipping the semantic tier — wiring components straight to primitives — is the most common mistake. It feels faster on day one and makes the first rebrand or dark-mode pass a rewrite.
  • Don't over-tokenize. A token for every one-off value is noise; tokenize decisions you'll reuse or theme, not every number on the screen.

Further reading