Design tokens: the contract between design and code
Most token systems fail silently — a Figma variable nobody renames, a hex value pasted into a component at 11pm. Here is the token architecture we use to keep design and code honest.
Sarah Chen
Head of Design
August 5, 2026
7 min read
Every team we have rescued had the same scar: a beautifully documented design system where, three levels deep in a real component, someone pasted `#64748B` directly into a className because "the muted token was not quite right". Tokens do not fail at setup. They fail at the edges, where the contract between design and code has no enforcement.
The three-layer model that survives contact with reality
We structure every token system in three layers, and the layering is the enforcement mechanism:
- →Primitives — raw values with no opinion: lime-400, ink-900, space-4. Never referenced by components.
- →Semantic — intent-based references: accent, surface-raised, text-muted, danger. This is the only layer components may use.
- →Component — optional aliases for genuinely component-specific values: button-height-lg, input-border-focus. Kept small on purpose.
:root {
/* semantic layer — the only layer components touch */
--accent: #a3e635;
--accent-text: #4d7c0f;
--surface-raised: #ffffff;
--text-muted: #5d6470;
}
.dark {
--accent: #a3e635;
--accent-text: #a3e635;
--surface-raised: #11141b;
--text-muted: #98a1b3;
}The rule that makes it work is social, not technical: a PR that introduces a raw hex value, pixel value, or z-index is a review blocker. We enforce it with stylelint and a grep in CI, but honestly the rule exists because it was typed into our CODEOWNERS file by a very tired designer after the 11pm incident.
Name tokens after intent, not appearance
`--color-lime-400` as a semantic token is a trap: the day your brand pivots from lime to cyan, you are renaming tokens across both Figma and code, or worse — living with `--color-lime-400: #22d3ee` and never forgiving yourself. Name by role: accent, accent-text, surface, border-strong. The primitive layer keeps the pretty names.
A token name is documentation that renders in autocomplete. If a developer has to open the design file to know what `--surface-3` means, the name has already failed.
Sync is a build step, not a meeting
We export Figma variables to JSON in a GitHub Action on the design-system repo, transform them into CSS custom properties, and open a PR. Design changes land as reviewable diffs with the same seriousness as code. The day the export PR shows a color change nobody remembers approving is the day the system proves its worth — it happened in week two, and it has paid for the entire pipeline twice over since.
- →Export Figma variables → JSON in CI (delta PRs, never auto-merge).
- →Transform → CSS custom properties + Tailwind theme mapping in one script.
- →Lint for raw values in component code; block the PR, not just warn.
- →Version the token package; changelog entries are one sentence each, written by whoever made the change.
The payoff is boring and enormous: our last project onboarded two new engineers who shipped their first themed page — dark mode included — on day two, without opening Figma once. That is the contract working as intended.
have-a-project-like-this
This is the kind of thinking we bring to client work. If it resonates, the discovery call is free.
Talk to uskeep-reading
Engineering · 8 min
Next.js 16 in production: what we learned shipping 12 apps
Turbopack is no longer optional, cache components change how you think about data, and partial prerendering quietly became the default. Our field notes from twelve production upgrades.
Product · 9 min
From MVP to v1: scoping your first 90 days
The MVP trap is real: products that launch as prototypes and stay that way. A field-tested framework for deciding what the first 90 days after launch should actually contain.