EmpireUI
Get Pro
← Blog8 min read#figma#css variables#design tokens

Figma Variables to CSS Custom Properties: The Workflow That Works

Stop copy-pasting hex values by hand. Here's the Figma-to-CSS variables workflow that actually holds up across a real design system.

Figma design file open next to a CSS code editor on screen

Why This Problem Is Still Unsolved in 2026

You'd think by now we'd have a clean pipe from Figma to CSS. We don't. The tooling has gotten better — Figma Variables shipped in 2023, Tokens Studio has been around for years — but the workflow most teams are actually running is still some combination of copy-pasting, a half-maintained Figma plugin, and a Slack message that says "wait, which blue is the primary one again?"

The core issue isn't technical. It's that design and code have different mental models for the same thing. A designer thinks about "primary/500" as a color swatch in a library. A developer thinks about --color-primary as something that gets resolved at runtime, maybe swapped per theme. Those two models need a translation layer, and that layer is design tokens.

Honestly, most teams skip the tokens layer entirely and pay for it later. They connect Figma directly to Tailwind config or hardcode hex values in a globals.css. Works until you need a dark mode, a rebrand, or a second product on the same codebase. Then it's six weeks of archaeology.

This article covers a workflow that actually scales: Figma Variables → JSON tokens → CSS custom properties → component usage. Each step is deliberate. You can automate as much or as little of it as you want.

Setting Up Figma Variables the Right Way

Figma Variables (as of the 2024.x desktop client) support four types: Color, Number, String, and Boolean. For a design-to-code workflow you mostly care about Color and Number. The thing most designers miss: collection structure is not just organisation, it maps directly to your token groups.

Create a collection called Primitives first. This is your raw palette — every color, spacing value, and radius you might ever use. No semantic meaning here. blue/100 through blue/900, spacing/4 through spacing/96. These map to --blue-100, --spacing-4 in CSS. Dead simple.

Then create a Semantic collection that references primitives. color/background/surface resolves to blue/50 in light mode and blue/950 in dark mode. This is where Figma's modes feature earns its keep. You define Light and Dark as two modes in one collection, and every semantic token has a value per mode. In CSS land, this becomes your :root and [data-theme="dark"] blocks.

Worth noting: don't put your component-level tokens (like button/primary/background) in the same collection as your semantic tokens unless your team is disciplined enough to maintain three levels of indirection. Most aren't. Two levels — primitives and semantic — is enough for 90% of projects.

Quick aside: Figma still doesn't export variables as JSON natively. You need a plugin. More on that next.

Exporting: Tokens Studio vs. the Variables API

Two realistic options in 2026: Tokens Studio (the plugin, free tier exists) or hitting the Figma Variables REST API directly with a personal access token. Both work. Which one you pick depends on whether you want a UI or a script.

Tokens Studio exports a tokens.json that looks roughly like this: ``json { "color": { "background": { "surface": { "$value": "{color.blue.50}", "$type": "color" } } } } ` The $value` uses reference syntax — it points to another token rather than a raw hex. That's what you want. If you're getting raw hex values at this stage, you've lost the graph and dark mode is going to hurt.

The Figma REST API approach (GET /v1/files/:key/variables/local) gives you more control but requires a paid Figma plan for full variable access. The response structure is more verbose, but you can pipe it through a small Node script and output whatever token format you need. In practice, I've found the API approach cleaner for teams that already have a CI pipeline, and Tokens Studio better for designers who want to publish tokens without involving an engineer every time.

Either way, your output target is Style Dictionary — the de facto standard for transforming design tokens into platform outputs. It handles the JSON-to-CSS conversion, and it's what we'll wire up next.

Transforming Tokens to CSS with Style Dictionary

Style Dictionary takes your tokens JSON and outputs CSS custom properties, JS objects, iOS Swift files, Android XML — whatever you need. For our workflow, we want CSS. The config is straightforward: ``js // style-dictionary.config.js module.exports = { source: ['tokens/**/*.json'], platforms: { css: { transformGroup: 'css', prefix: 'ds', buildPath: 'src/styles/tokens/', files: [ { destination: 'variables.css', format: 'css/variables', options: { outputReferences: true } } ] } } }; ` The outputReferences: true flag is critical — it preserves the reference chain so --ds-color-background-surface resolves to var(--ds-color-blue-50) rather than the raw #EFF6FF`. That means you can override primitives at runtime and everything cascades correctly.

Run npx style-dictionary build and you get a variables.css file with every token as a custom property. Drop it in your layout root or import it in your globals.css, and you're done with the generation step.

For dark mode, you need one extra transform. Style Dictionary doesn't understand Figma modes natively, so you'll write a custom transformer that splits tokens by mode and wraps them in the appropriate selector: ``js const darkTokens = tokens.filter(t => t.mode === 'dark'); // output to [data-theme="dark"] { ... } ` Alternatively, if you're using Tokens Studio, their sd-transforms` package handles mode splitting for you out of the box as of v0.14. Worth checking before writing your own.

In practice, a single npm run tokens script that runs the Figma API fetch and Style Dictionary build back-to-back is all you need to keep things in sync. You don't need a fancy CI job on day one — just a script you can run before a release.

Using the Variables in Your Components

Now the fun part. Your CSS file has variables like --ds-color-background-surface, --ds-spacing-4, --ds-radius-md. In your components you reference these directly — no magic, no abstraction: ``css .card { background: var(--ds-color-background-surface); padding: var(--ds-spacing-4); /* resolves to 16px */ border-radius: var(--ds-radius-md); /* resolves to 8px */ border: 1px solid var(--ds-color-border-subtle); } ` Notice you're referencing semantic tokens, not primitives. A component should never reach for --ds-blue-50` directly. That coupling means you can't retheme without touching component code.

For Tailwind users, you can bridge the two worlds by pointing your tailwind.config.js at the same tokens: ``js // tailwind.config.js module.exports = { theme: { extend: { colors: { surface: 'var(--ds-color-background-surface)', 'surface-raised': 'var(--ds-color-background-surface-raised)', } } } } ` Now bg-surface in a Tailwind class resolves through the CSS variable, which resolves through the token graph. Dark mode works automatically via the [data-theme="dark"]` selector.

Look, you don't have to use every token. If your team has 200 spacing tokens and you're only ever using 8 of them, that's a Figma organisation problem, not a CSS problem. The workflow works best when the Figma library is actually maintained — meaning someone is the owner and tokens don't proliferate unchecked.

One more thing — if you're building a component library and want to see how token-driven styling looks in practice, check out the glassmorphism components in Empire UI. They're built on CSS custom properties throughout, which is exactly why they're trivially themeable.

Automating the Sync: Keeping Figma and Code in Step

The manual version of this workflow — designer changes a token in Figma, exports JSON, hands it to a dev, dev runs the transform, opens a PR — works fine for small teams. It breaks down around 5+ designers or 10+ component libraries. At that scale you want the sync automated.

The simplest automation: a GitHub Action that runs on a schedule (or via a webhook from Figma) that fetches variables from the API, runs Style Dictionary, and opens a PR if anything changed. The PR diff shows you exactly which tokens changed, which is valuable for reviewing breaking changes: ``yaml # .github/workflows/sync-tokens.yml name: Sync Design Tokens on: schedule: - cron: '0 9 * * 1-5' jobs: sync: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Fetch Figma Variables run: node scripts/fetch-figma-variables.js env: FIGMA_TOKEN: ${{ secrets.FIGMA_TOKEN }} FIGMA_FILE_KEY: ${{ secrets.FIGMA_FILE_KEY }} - name: Build Tokens run: npx style-dictionary build - name: Open PR if changed uses: peter-evans/create-pull-request@v6 with: title: 'chore: sync design tokens from Figma' branch: 'sync/design-tokens' `` This runs every weekday at 9am. Designers update Figma whenever. The PR appears automatically. Someone reviews it, merges it, done.

Worth noting: Figma webhooks exist but they're scoped to file events, not variable-specific changes. You'll get noise from every file save. The scheduled approach is quieter and less likely to flood your PR queue.

If your team uses the Empire UI gradient generator or other tools that generate CSS values, those outputs slot right into this same token pipeline — just add the generated value as a primitive token and reference it semantically.

Common Pitfalls and How to Sidestep Them

The most common mistake: using raw hex values anywhere in component CSS after you've set up the token system. It seems harmless — one color: #1a1a1a buried in a button component. Six months later that button doesn't respond to theme changes and nobody knows why. Lint for it. There are ESLint plugins for CSS-in-JS and Stylelint rules for vanilla CSS that'll catch raw color values.

Second pitfall: token name collisions when you add a second product or sub-brand. If both products share a token named --ds-color-primary, you're going to have a conflict at some point. Namespace by brand early: --brand-a-color-primary, --brand-b-color-primary. Yes, it's more verbose. It saves you a refactor.

The third one is subtler. Figma Variables have a concept of scoping — you can restrict which variable types a variable shows up for (fill, stroke, effect, etc.). If your developers are exporting variables and wondering why some tokens are missing, check the scoping settings. A variable scoped only to "Effects" won't appear for a designer trying to use it on a fill, and depending on your export plugin it might get filtered out of the JSON too.

Finally: keep your tokens.json in version control, even if it's generated. You want a history of token changes. A diff between two commits showing --ds-color-primary shifting from #3B82F6 to #2563EB is a meaningful record. You can also reference that history in design changelog discussions — it's the design system documentation equivalent of a git blame for visual decisions.

FAQ

Do I need a paid Figma plan to use Variables for this workflow?

For the REST API approach to variables, yes — the /variables/local endpoint requires a paid plan. Tokens Studio as a plugin works on any plan, including free, since it reads variables through the plugin API.

Can I use this workflow with Tailwind CSS?

Absolutely. Point your tailwind.config.js theme values at CSS custom properties instead of raw hex codes. Tailwind outputs the variable references and your token system handles the actual values at runtime.

How do I handle dark mode tokens from Figma?

Set up a two-mode collection in Figma (Light and Dark), then use either the sd-transforms package from Tokens Studio or a custom Style Dictionary transformer to split the output into :root and [data-theme='dark'] blocks.

What's the difference between primitive tokens and semantic tokens?

Primitives are raw values — blue/500: #3B82F6. Semantics give them meaning — color/action/primary: {blue/500}. Components should only reference semantic tokens so you can retheme without touching component code.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

CSS Custom Properties: Dynamic Theming and Animation TricksAdvanced CSS Custom Properties: @property, Animatable TokensDesign Tokens in 2026: From Figma Variables to CSS Custom PropertiesFigma to Code Workflow 2026: Variables, Dev Mode and Tokens Studio