All articles
Design & UXJuly 18, 2026 6 min read

Design Tokens That Survive Contact With Engineering

Most design token systems die somewhere between Figma and production. Here's how we structure tokens so they actually round-trip between designers and engineers without becoming a second source of truth nobody trusts.

Design Tokens That Survive Contact With Engineering

Every design system starts with the same optimistic promise: one source of truth for color, spacing, and type. Six months in, the truth is scattered across a Figma library, a theme.ts file, three Tailwind configs, and a Slack thread where someone asked why the primary button is suddenly teal on Android.

Design tokens are supposed to fix this. Most implementations we inherit haven't. Here's what actually holds up.

Why most token systems drift

The usual failure mode isn't laziness — it's that tokens get treated as a naming exercise instead of an architecture problem. A team picks names like blue-500, dumps them into a JSON file, mirrors them in Figma variables, and calls it done. Then reality shows up:

  • A designer needs a warning color that isn't yellow-500 but a slightly desaturated version for dark mode.
  • An engineer needs to know which blue to use for a link vs. a button vs. a focused input.
  • Marketing ships a landing page with its own palette, because the system doesn't cover promotional surfaces.

The tokens didn't fail. The layering did. Tokens without hierarchy are just variables with better PR.

The three-tier model that works

We've landed on the same structure across most projects: primitive, semantic, and component tokens. It's not novel — Adobe Spectrum and Salesforce Lightning have been talking about it for years — but the discipline of keeping the tiers separate is where teams slip.

  • Primitives: raw values. color.blue.500 = #3B82F6. space.4 = 16px. No meaning attached.
  • Semantic: intent. color.background.surface, color.text.muted, color.border.focus. These reference primitives.
  • Component: local. button.primary.background, input.border.error. These reference semantic tokens.

Components never reach past semantic. Semantic never hard-codes hex. If a designer wants to change the entire brand from blue to purple, they change one primitive and everything downstream follows. If they want to make focus rings more prominent, they change one semantic token.

The Figma → code handoff that doesn't lie

Figma Variables (shipped in 2023, matured since) finally made it possible to define tokens once and reference them across modes. But Figma is not your source of truth. Neither is your codebase. The source of truth is a format-agnostic token file — usually JSON following the W3C Design Tokens spec — that both sides sync against.

Here's the flow we run:

tokens/
  primitives.json   ← raw values
  semantic.json     ← references primitives
  components.json   ← references semantic
  themes/
    light.json
    dark.json
    high-contrast.json

From that, Style Dictionary (or Terrazzo, if you want something newer) emits:

  • tokens.css with custom properties
  • tailwind.config.js extensions
  • tokens.ts for typed access in JS
  • A Figma-compatible JSON for the Tokens Studio plugin

The designers edit tokens in Figma via Tokens Studio, push to Git, CI regenerates the platform outputs, and the PR shows up in the frontend repo. No one is manually copying hex codes.

A minimal Style Dictionary config

// build-tokens.mjs
import StyleDictionary from 'style-dictionary';

const sd = new StyleDictionary({
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      buildPath: 'dist/css/',
      files: [{
        destination: 'tokens.css',
        format: 'css/variables',
        options: { outputReferences: true }
      }]
    },
    tailwind: {
      transformGroup: 'js',
      buildPath: 'dist/tailwind/',
      files: [{
        destination: 'tokens.js',
        format: 'javascript/module-flat'
      }]
    }
  }
});

await sd.buildAllPlatforms();

outputReferences: true is the important flag. Without it, Style Dictionary flattens everything into raw values and you lose the ability to theme via CSS custom properties at runtime. With it, you get:

:root {
  --color-blue-500: #3b82f6;
  --color-background-brand: var(--color-blue-500);
  --button-primary-background: var(--color-background-brand);
}

Change --color-blue-500 in a dark-mode scope and everything cascades.

Naming: the part everyone underestimates

Token names are an API. Once shipped, renaming them is a migration. A few rules we enforce:

  1. Semantic tokens describe role, not appearance. color.text.danger not color.text.red. Red might become orange in high-contrast mode.
  2. State goes at the end. button.primary.background.hover, not button.hover.primary.background. Consistent suffix ordering makes autocomplete usable.
  3. Avoid scale numbers in semantic tokens. space.gutter.md is fine. color.surface.500 is not — 500 means nothing at the semantic layer.
  4. Never use brand names. color.brand.primary outlives a rebrand. color.electricPurple does not.

The smell test: can a new engineer guess the token from the design intent? If a designer says "put this on a subdued background," they should be able to reach for color.background.subtle without asking.

Where Tailwind fits

Tailwind v4 pushed most of its configuration into CSS via @theme, which lines up well with a token-first workflow. The generated tokens.css becomes the input:

@import "tailwindcss";

@theme {
  --color-surface: var(--color-background-surface);
  --color-surface-subtle: var(--color-background-subtle);
  --color-text-default: var(--color-text-default);
  --color-text-muted: var(--color-text-muted);
  --spacing-gutter: var(--space-gutter-md);
}

Now bg-surface, text-muted, and p-gutter are all available in JSX, and they resolve through the same variable chain the design system controls. No theme.extend block that drifts.

Handling themes without a rewrite

A token system earns its keep the day someone asks for a dark mode, a white-label variant, or a WCAG AAA build. If your semantic tokens are done right, themes are just remapping the middle layer:

// themes/dark.json
{
  "color": {
    "background": {
      "surface": { "value": "{color.slate.900}" },
      "subtle":  { "value": "{color.slate.800}" }
    },
    "text": {
      "default": { "value": "{color.slate.50}" },
      "muted":   { "value": "{color.slate.400}" }
    }
  }
}

No component knows about dark mode. No conditional classes. The theme file swaps which primitives the semantic layer points at, and the cascade handles the rest.

This is also where accessibility work gets cheaper. A high-contrast theme is a third JSON file. Contrast audits run against the semantic layer, not against every component instance. When we've audited token systems for clients, projects with a proper semantic layer typically resolve WCAG AA gaps in days rather than weeks — mostly because you're fixing tokens, not chasing components.

The governance part nobody wants to talk about

A token system with no owner rots. Some practices that have kept ours healthy:

  • One repo, one PR flow. Tokens live in the design system repo. Changes go through code review, even if the diff is a color value. Designers get commit access; they don't get a bypass.
  • Deprecation, not deletion. When a token retires, mark it @deprecated in the JSON with a replacement field. The build emits a warning. Give consumers a sprint or two.
  • A changelog aimed at both audiences. "Renamed color.text.subduedcolor.text.muted" means something to engineers. "Increased contrast on secondary text from 4.1:1 to 4.8:1" means something to designers. Write both.
  • Visual regression tests on the primitives. If someone bumps color.blue.500, you want to see every button, badge, and link in a screenshot diff before it merges.

Where we'd start

If you're staring at a codebase full of hex codes and a Figma library that doesn't match, don't try to boil the ocean. Pick the smallest useful slice:

  1. Audit your current colors and spacing. Dedupe. You probably have 40 shades of gray that should be 6.
  2. Define the three-tier structure in JSON. Start with color and spacing only. Type and motion can wait.
  3. Wire up Style Dictionary (or equivalent) with one output — usually CSS variables. Ship it.
  4. Migrate one component. A button is a good candidate. Prove the round-trip works.
  5. Only then bring Figma Variables into the loop via Tokens Studio.

The teams we've seen succeed treated tokens as infrastructure, not documentation. If you want a second pair of eyes on your setup — or help unwinding one that's already drifted — that's the kind of work our design and frontend teams tend to enjoy. Everyone else, keep your semantic layer honest and your primitives boring.

#Design Systems#Design Tokens#Figma#Tailwind#Frontend

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project