All articles
Design & UXOctober 1, 2026 6 min read

Figma Variables to Tailwind Tokens: A Workflow That Actually Survives Handoff

Design tokens die at the handoff boundary. Here's the Figma Variables → Tailwind pipeline we use so color, spacing, and radius changes make it to production without a designer filing a Jira ticket.

Figma Variables to Tailwind Tokens: A Workflow That Actually Survives Handoff

Every design system we've shipped eventually hits the same wall: a designer renames brand/primary to accent/default, pushes it to the Figma library, and nothing happens in the codebase. A week later a PM asks why the new button color isn't live. The honest answer is that most teams never actually built a pipeline — they built a convention and hoped.

This is the workflow we use at 72Technologies to move Figma Variables into Tailwind without a human translation step in the middle. It's opinionated, it has sharp edges, and it works.

Why the old way broke

For years the handoff looked like this: designer picks colors in Figma, engineer eyeballs the hex, pastes it into tailwind.config.js, maybe adds a comment with the Figma style name. The system drifts within a sprint.

Figma Variables (GA since 2024) changed the ceiling of what's possible because tokens now have:

  • Real names with collections and modes (light/dark, density, brand)
  • Aliases (one variable referencing another)
  • A REST API you can hit from CI

Tailwind v4 met it halfway by moving configuration into CSS with @theme, which maps almost 1:1 to how Figma thinks about tokens. The gap between the two tools is now small enough to automate.

The pipeline, end to end

Here's the shape of what we build on every design-system project:

  1. Designers own a Figma file called Tokens (nothing else lives there).
  2. A GitHub Action pulls variables via the Figma REST API on a schedule or webhook.
  3. A transform script converts the Figma JSON into a W3C Design Tokens format file.
  4. Style Dictionary (or a small custom script) emits a tokens.css file with CSS custom properties.
  5. Tailwind's @theme block consumes those custom properties.
  6. A PR is opened automatically. A human reviews and merges.

That last step matters. We don't auto-merge token changes. A rename can cascade through dozens of components, and we want eyes on it.

Why not Token Studio?

Token Studio is good, and if your team is already deep in it, keep going. We've moved away from it on new projects because Figma Variables now cover the cases that used to require a plugin, and one less tool in the chain means one less thing that breaks when Figma ships an update.

Naming: the part everyone gets wrong

If you take one thing from this piece, take this: your token names should describe role, not appearance. color/surface/raised survives a rebrand. color/gray/100 does not.

We use a three-tier structure:

  • Primitive: color.slate.500, space.4. Raw values. Never used in components directly.
  • Semantic: color.surface.default, color.text.muted, space.inline.sm. Reference primitives. This is what components consume.
  • Component (optional): button.primary.bg, card.padding. Reference semantics. Only used when a component needs to diverge.

In Figma, these become three collections. In Tailwind, only the semantic and component tiers get exposed as utility classes. Primitives stay as CSS variables that nobody writes by hand.

A good heuristic: if a designer can't change a token's value without renaming it, the name is wrong.

The export script

Figma's API returns variables as a nested JSON blob with internal IDs for aliases. You have to resolve those yourself. Here's the core of our transform, trimmed for clarity:

import { writeFileSync } from 'node:fs';

type FigmaVariable = {
  name: string;
  resolvedType: 'COLOR' | 'FLOAT' | 'STRING';
  valuesByMode: Record<string, FigmaValue>;
};

async function fetchVariables(fileKey: string, token: string) {
  const res = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/variables/local`,
    { headers: { 'X-Figma-Token': token } }
  );
  if (!res.ok) throw new Error(`Figma API: ${res.status}`);
  return res.json();
}

function toW3CToken(variable: FigmaVariable, modeId: string) {
  const value = variable.valuesByMode[modeId];
  const type = variable.resolvedType === 'COLOR' ? 'color' : 'dimension';

  return {
    $type: type,
    $value: isAlias(value)
      ? `{${resolveAliasPath(value)}}`
      : formatValue(value, type),
  };
}

The full version handles alias resolution across collections, mode merging for light/dark, and a sanity check that fails the build if any variable name contains a space or capital letter. That last rule has saved us more than once.

Feeding Style Dictionary

Once you have W3C-formatted tokens, Style Dictionary does the rest. Our config emits a single CSS file:

export default {
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      buildPath: 'src/styles/',
      files: [{
        destination: 'tokens.css',
        format: 'css/variables',
        options: { selector: ':root' },
      }],
    },
  },
};

And Tailwind picks it up:

@import 'tailwindcss';
@import './tokens.css';

@theme {
  --color-surface-default: var(--color-surface-default);
  --color-surface-raised: var(--color-surface-raised);
  --color-text-default: var(--color-text-default);
  --color-text-muted: var(--color-text-muted);
  --spacing-inline-sm: var(--space-inline-sm);
}

The double-reference feels redundant but it's deliberate: @theme is what generates the utility classes (bg-surface-raised, text-muted), and the right-hand side points at the CSS variable so dark mode and runtime theming still work.

Modes, dark mode, and the thing nobody warns you about

Figma lets you have arbitrary modes on a collection. Most teams use light and dark. Some add high-contrast or brand variants.

The gotcha: Figma stores modes per collection, and a semantic token in one collection can alias a primitive in another. If your semantic collection has light/dark modes but your primitive collection only has one mode, aliases resolve fine. If both have modes, you need to decide which one wins, because Figma will happily let you create a matrix of combinations that CSS cannot express without a build step per mode.

Our rule: only the semantic layer has modes. Primitives are mode-less. This keeps the generated CSS to a single :root block plus a [data-theme="dark"] block, and it matches how Tailwind's dark: variant works out of the box.

Catching regressions before they ship

Automated token sync will eventually generate a PR that silently kills your contrast ratios. We run two checks in CI on every token PR:

  • Contrast audit: for every color.text.* + color.surface.* pairing we expect to coexist, assert WCAG AA (4.5:1 for body, 3:1 for large text). We use a small script wrapping the wcag-contrast package.
  • Visual regression on a Storybook of core components, via Chromatic or Playwright screenshots. Token renames are invisible to unit tests but obvious to a pixel diff.

If you only do one, do the contrast audit. We've caught a text.muted change that would have dropped a secondary label to 3.8:1 against a card background — perfectly legal in Figma, failing in production.

What this costs you

Honest trade-offs:

  • Initial setup is roughly a week of design-engineering work, mostly spent arguing about naming.
  • Designers lose the ability to "just try something" in a production file. The Tokens file becomes a committed artifact, not a sketchpad.
  • You need someone who owns the pipeline. When Figma changes its API response shape (it will), somebody has to fix the transform.

The payoff is that a rebrand becomes a one-day job instead of a two-sprint project, and dark mode stops being a feature request that scares everyone.

Where we'd start

If you're staring at a codebase with 60 hand-maintained color classes and a Figma file full of local styles, don't try to build the whole pipeline on day one. Do this instead:

  1. Pick one surface — say, the marketing site or the settings panel — and define its semantic tokens first. Ten to fifteen is plenty.
  2. Create those as Figma Variables and as CSS custom properties by hand. No automation yet.
  3. Refactor that one surface to use them. Measure how often values change over two weeks.
  4. Only then build the sync pipeline, because now you know what shape your tokens actually take under real pressure.

Tokens are a product, not a config file. Treat the pipeline like one and it'll pay rent for years. If you want help scoping this for your team, our design systems work is where we usually start these conversations.

#Design Systems#Tailwind#Figma#Design Tokens#Workflow

Want a team like ours?

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

Start a project