Skip to content
Back to Blog Figma

Figma Variables to CSS Custom Properties: A Practical Guide

Sofia Dragomir 8 min read
Figma variables mapping to CSS custom properties

Figma Variables and CSS custom properties look similar. Both store named values. Both support aliasing. Both have a role in a token-based design system. But they are not the same thing, and treating them as if they were creates subtle synchronization bugs that take time to find and fix.

This guide covers what the differences are, how to map between them correctly, and where the edge cases live. It assumes you are using Figma Variables alongside a token transformation pipeline like Style Dictionary, and that you care about keeping design and code synchronized rather than just exporting a snapshot.

How Figma Variables differ from CSS custom properties

Figma Variables exist inside Figma's data model. They have a type (Color, Number, String, Boolean), they belong to a collection, and they can be scoped to specific properties on components. A color variable in Figma is a Figma-native concept: it carries metadata about where it can be applied, whether it is published for use by other files, and which modes it has values for.

CSS custom properties are strings in a cascade. --color-primary: #3DFFA3 defines a value at a specific cascade scope, and any CSS property can reference it with var(--color-primary). The CSS spec does not know about types, collections, or modes. It knows about specificity, inheritance, and whether a value resolves.

The meaningful consequence: Figma Variables can have modes (light and dark, for example). A single variable name resolves to a different value depending on which mode is active. CSS custom properties can replicate this by defining the same property name inside different selectors or on different elements with class-based overrides, but the mechanism is different and the relationship is not automatic.

The naming translation problem

Figma Variables are named using slash-separated paths by convention: Color/Brand/Primary/Default. CSS custom properties use kebab-case with a double-hyphen prefix: --color-brand-primary-default. These two naming formats need a consistent translation rule, and that rule has to be applied everywhere.

The translation seems trivial: lowercase, replace slashes with hyphens, prepend double hyphen. It becomes non-trivial when the Figma naming is inconsistent. If some colors use Color/Brand/Primary/Default and others use colors/primary/brand, the transformation produces two different CSS property names for conceptually similar tokens. The resulting codebase has no consistent naming pattern.

The fix is to enforce Figma Variable naming conventions before transformation, not after. Decide on a single hierarchy depth and structure for each token type. Color variables: Color/Role/Scale/Variant. Spacing variables: Spacing/Scale/Name. Type variables: Type/Scale/Size. Run the export. Transform. Review the CSS output. If the names read clearly and consistently, the naming convention is working.

Boolean variables and the code gap

Figma Variables support a Boolean type, which Figma uses to show and hide layers. There is no direct CSS equivalent. A Boolean variable that controls whether a component shows an icon does not map to a CSS custom property because CSS does not conditionally display elements based on custom property boolean values.

Boolean Figma Variables should not be in your token export at all. They control design-side display logic that gets replaced by React props or conditional rendering in code. When setting up your Tokens Studio config or Figma REST API export, filter out Boolean variables explicitly. Including them in the output creates dead weight in your CSS file and confuses anyone reading the token list.

Mode mapping: the hardest part

Figma Variable collections can have multiple modes. A color collection might have a Light mode and a Dark mode. When you export variables, both modes need to produce CSS output. The question is: how does the CSS represent the mode switch?

The two main patterns are class-based and data-attribute-based overrides. Class-based: .theme-dark { --color-brand-primary: #2ECC85; }. Data-attribute-based: [data-theme="dark"] { --color-brand-primary: #2ECC85; }. Both work. The important thing is picking one and configuring Style Dictionary or your transformation script to produce that pattern consistently.

Where this gets complicated: Figma mode names are arbitrary strings. A Figma collection might have modes called "Default", "Dark", "High Contrast", and "Brand A". The CSS selector names need to be derived from these mode names in a predictable way. Default becomes no selector (just :root). Dark becomes [data-theme="dark"]. High Contrast becomes [data-theme="high-contrast"]. Brand A becomes [data-theme="brand-a"]. This mapping needs to be explicit in your transformation config. If it is implicit, it will break when a mode name changes.

Number variables and unit handling

Figma Number variables store unit-less numbers. A spacing variable with a value of 16 in Figma means 16px. But the correct CSS representation might be 1rem, 16px, or even 0.16rem depending on your base font size and project convention.

Style Dictionary handles this with transform functions. You define a transform that takes a number token, applies a conversion rule (divide by 16 for rem, append px for pixel), and produces the correct output string. The transform needs to know which token types should be converted and which should remain unit-less.

The pitfall is applying the wrong transform to a number variable that stores something other than a spacing or size value. An opacity token with a value of 0.5 should not have "px" appended. A z-index token with a value of 100 should remain a unit-less integer. Your transform configuration needs to distinguish between token types with explicit rules, not just pattern-match on all Number variables.

When Figma Variables export is not enough

The Figma REST API's Variables endpoint exports the variable collection as JSON. For a simple system with one collection and two modes, this JSON is straightforward to process. For a system with multiple collections, cross-collection aliases, and complex mode hierarchies, the raw JSON export becomes difficult to work with directly.

Tokens Studio adds a processing layer: it reads Figma Variables (or maintains its own JSON alongside the design file) and produces a more structured token set. The Tokens Studio JSON format is better supported by Style Dictionary's standard transforms. If you are working with a complex multi-collection variable system, Tokens Studio's layer reduces the processing work you have to do in your own transform scripts.

That said, Tokens Studio is not a requirement. If your variable structure is simple and your team maintains the token JSON manually, a direct Figma Variables export processed by a custom Style Dictionary config is workable. The choice depends on team workflow, not on technical necessity.

Keeping the two sides in sync over time

The initial mapping is the easier problem. Keeping the mapping synchronized as both the design file and the codebase evolve is where most teams encounter sustained friction.

The pattern that reduces drift: treat the Figma Variables export as the authoritative source and regenerate the CSS on every token change. Do not manually edit the generated CSS file. If a value needs to change, change it in the Figma Variables definition, export, transform, commit. This requires a stable export process and CI integration, but it makes the CSS output a reliable reflection of the design state rather than a diverged snapshot.

The scenario to watch for: a developer modifies a CSS custom property value directly to fix a one-off visual issue, the change goes into production, and later the design file and the CSS are out of sync. Nobody knows which is correct. The generated file should be marked as generated in its header comment so engineers know not to edit it, and the CI step should catch if a generated file has been manually modified.

DesignVerse's import pipeline enforces this by treating the token file as the single source of truth and regenerating all component output on every import. The CSS never exists as a file you edit, only as a file you receive. That constraint removes the possibility of the drift scenario from the workflow entirely.

Stop losing hours in handoff.

Connect your design system today and generate your first components in minutes.

Free plan available. No credit card required.