Design tokens that actually scale: lessons from five design system builds
Token architecture is not a Figma problem. It's an engineering problem. Here's how we solve it.
We've built five design systems from scratch and inherited twelve. The ones that scale have one thing in common: the token architecture was designed by someone who understood both the design constraints and the engineering constraints. Here's the model we use.
- Three token tiers: primitive, semantic, component
- CSS custom properties are the correct delivery format
- Name for meaning, not for value
- Version control is non-negotiable
Three token tiers: primitive, semantic, component
The most durable design systems separate raw values (primitives), their meaning (semantic tokens), and their application (component tokens). A primitive is a hex value. A semantic token is 'color.background.interactive'. A component token is 'button.primary.background.default'.
The abstraction costs about three extra hours at system setup. It saves hundreds of hours when you rebrand, add a dark mode, or need to white-label the product for an enterprise client.
CSS custom properties are the correct delivery format
We've used every delivery format — Sass variables, JavaScript constants, JSON, Style Dictionary, Theo. CSS custom properties (CSS variables) have won in every project for the last four years.
They're inspectable in the browser. They cascade. They can be overridden at any scope level. They respond to the system `prefers-color-scheme` media query without any JavaScript. They're supported in every browser worth supporting. The developer experience tooling (CSS IntelliSense, Tailwind config integration) is excellent.
Name for meaning, not for value
`color.gray.500` is a primitive. `color.text.secondary` is a semantic token. The first one tells you what the value is. The second one tells you when to use it.
An engineer using `color.gray.500` directly in a component has to make a design decision — they're choosing a colour. An engineer using `color.text.secondary` is following a design decision that's already been made. The semantic layer moves design decisions out of component code and into the design system, where they belong.
Version control is non-negotiable
We version every design system we build. Token changes that break component APIs are breaking changes. They get a major version bump, a migration guide, and a deprecation notice in the changelog.
Engineering teams that treat design system tokens as mutable configuration don't understand what they've built. A token is an API contract between the design layer and the implementation layer. Changing a semantic token's value is a breaking change that deserves the same rigour as a REST API version bump.