Skip to the content.

Design tokens are the API of a design system

Design tokens are the API of a design system

A design token is a named value — color.brand, space.md, radius.card — used everywhere in place of a raw #fe854c or 16px. That sounds like a naming convention, but the useful way to see it is as an API: tokens are the design system’s public interface, and components are consumers that only ever reference the names, never the underlying values. Draw that line and a rebrand, a dark mode, or a density change becomes editing the implementation behind the API — a config change in one place — instead of a find-and-replace across a thousand components that will always miss some.

Components consume token names; the token layer maps names to values Components reference token names like color.brand and space.md. A token layer maps those names to concrete values, and a theme swaps the values without touching components. componentsuse names only color.brand token layer (API)name → value light: #fe854c dark: #ff9a63 swap values here — components never change
Components depend on the token names; the token layer resolves names to values. Change the values (a theme) and the consumers are untouched.

Components consume names, never raw values

The discipline is that no component contains a literal colour, spacing, or radius — it references a token. In CSS the natural carrier is a custom property, because it is a live, inheritable name:

:root {
  --color-brand: #fe854c;
  --space-md: 16px;
  --radius-card: 8px;
}
.card {
  padding: var(--space-md);        /* the NAME, not 16px */
  border-radius: var(--radius-card);
  border-color: var(--color-brand);
}

A reviewer (or a lint rule) can now enforce a simple invariant: a raw hex or px value in a component is a bug, because it bypasses the API.

Tiered tokens: primitives, semantics, components

Mature systems layer the API so intent is expressed, not just values. Primitive tokens are the raw palette (--blue-500); semantic tokens name a role (--color-action--blue-500); component tokens name a specific use (--button-bg--color-action). Components consume the semantic or component tier, so you can change what “action” means without touching the palette or the components:

:root {
  --blue-500: #1e6bb8;             /* primitive: a raw colour */
  --color-action: var(--blue-500); /* semantic: the ROLE */
  --button-bg: var(--color-action);/* component: this specific use */
}
.button { background: var(--button-bg); }

The payoff: theming and rebrands become config

Because components only touch the API, changing the implementation reprices the whole system at once. Dark mode redefines the semantic tier; a rebrand redefines the primitives; a density change redefines the spacing scale — each is one block of overrides, and every component inherits the change with zero edits:

[data-theme="dark"] {
  --color-action: #4c9be8;   /* re-point one semantic token; every button follows */
}

That is the whole argument for treating tokens as an API rather than as shared constants: an API has a stable surface (the names) and a swappable implementation (the values), which is exactly the property that turns “rebrand the app” from a multi-week grep into a config edit. Tokens are also what let design and engineering share one vocabulary — the designer’s “action colour” and the developer’s --color-action are the same token. The theme-toggle exercise builds the theme-swap-behind-the-API move directly, which is the clearest demonstration of why the indirection pays for itself.