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 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.