all-posts
Design Systems#Design tokens#Figma#Tailwind CSS

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.
css
: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;
}
Semantic tokens flip per theme; primitives never do. Components stay theme-agnostic.

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 us