All guides

Figma variables

Tailwind colors to Figma variables: migration guide

Tailwind's palette is a great starting point, but pasting hex codes into Figma is not a design system. Here is the two-collection structure that survives handoff.

Published 9 min read

Tailwind ships a well-tested color palette, and it is tempting to recreate it in Figma as a single list of swatches. That works for a mood board and falls apart in a real file: engineers rename shades, dark mode arrives, and nobody can tell whether gray-700 means a border or body text. The fix is to keep Tailwind's raw values as primitives and put a semantic layer on top of them.

Why copy the palette at all?

You could start from your brand colors instead, and for a greenfield project you probably should. Teams migrate Tailwind palettes because the palette already exists in production code, already passed contrast reviews in some places, and already has names engineers recognize. Keeping the palette as primitives means the migration does not force a redesign on day one. It just gives the same values a better structure.

  • Primitives answer what color is this? They are stable and boring: blue/500, neutral/50, red/600.
  • Semantic tokens answer where is this color used? They are the names designers and engineers actually reach for: text/primary, background/subtle, border/default.
  • Component tokens answer which component owns this? They are optional and should come last, once a pattern repeats enough to deserve a name.

1. Get the exact values out of Tailwind

Start from the values your app actually ships, not from a screenshot of the Tailwind website. In Tailwind v4 the default palette lives in node_modules/tailwindcss/theme.css and uses OKLCH. In v3 it is exported from tailwindcss/colors as plain hex.

Tailwind v4 default palette (excerpt)
@theme {
  --color-blue-500: oklch(62.3% 0.214 259.815);
  --color-neutral-50: oklch(98.5% 0 0);
  --color-neutral-950: oklch(14.5% 0 0);
  --color-red-600: oklch(57.7% 0.245 27.325);
}

Figma variables store colors as sRGB (or Display P3) values, not OKLCH, so convert before creating variables. A one-off script with a color library is fine; the important part is that the conversion is done once and recorded, not eyeballed per swatch. If you would rather not hand-copy, Huekit's Tailwind preset builds the palette as Figma variables for you.

2. Build the Primitives collection

One collection, one mode, one variable per Tailwind swatch. Keep the names mechanical so nobody has to guess. The values below are Tailwind v4's OKLCH colors converted to sRGB:

Tailwind v4 theme variableFigma variablesRGB value
--color-blue-500blue/500#2b7fff
--color-neutral-50neutral/50#fafafa
--color-neutral-950neutral/950#0a0a0a
--color-red-600red/600#e7000b
  • Set every primitive's scopes to none. Primitives are implementation details; hiding them from the color picker pushes people toward semantic tokens.
  • Do not add Light and Dark modes here. A primitive has one value. Modes belong on the semantic layer.
  • If your brand palette deviates from Tailwind, keep the same scale names and change the values. Renaming blue to brand is a separate decision; do it before the migration, not halfway through.

3. Add a Semantic collection with Light and Dark modes

This is where the design system starts. Create a second collection with two modes, Light and Dark, and make every variable an alias of a primitive. Keep the count small at first; a dozen well-chosen tokens cover most product UI.

Semantic variableLight aliasDark alias
background/defaultneutral/50neutral/950
background/subtleneutral/100neutral/900
surface/raisedwhiteneutral/800
text/primaryneutral/950neutral/50
text/secondaryneutral/600neutral/400
border/defaultneutral/200neutral/800
accent/defaultblue/600blue/400
accent/foregroundwhiteneutral/950

Notice that dark mode does not simply invert the scale. Surfaces move toward neutral/950, but accents usually move lighter (blue/600 to blue/400) because saturated colors lose contrast against dark backgrounds. This asymmetry is why a single dark: override per component is a poor substitute for a mapped token.

4. Wire modes without renaming variables

The whole point of modes is that the variable name stays the same while the value changes per context. A card uses background/default; the page or frame sets the mode to Light or Dark, and nothing in the component needs a conditional. When you preview both modes, look for missing aliases: an unset mode falls back to the default value and is easy to miss on a single screen.

For a deeper pass on naming and contrast, read Light and dark semantic tokens. It covers the role-based naming rules and the pairs that break in dark mode.

5. Keep Figma and code in sync

Two sources of truth become three within a month. Pick one direction and automate it. Exporting Figma variables as Design Tokens JSON is the most portable option because the format is tool-agnostic and supports aliases.

Design Tokens JSON
{
  "color": {
    "neutral": {
      "50": { "$type": "color", "$value": "#fafafa" },
      "950": { "$type": "color", "$value": "#0a0a0a" }
    },
    "text": {
      "primary": { "$type": "color", "$value": "{color.neutral.950}" }
    }
  }
}

On the code side, map the token file into Tailwind v4's @theme inline block instead of maintaining a second palette. inline makes utilities read the variable at the element, so theme switches flow through. Slashes in Figma names become hyphens in CSS custom properties, so text/primary becomes --color-text-primary. Keep that convention written down; it is the kind of detail that silently drifts.

Tailwind v4 theme mapped to token variables
@import "tailwindcss";

@theme inline {
  --color-background-default: var(--background-default);
  --color-text-primary: var(--text-primary);
  --color-accent-default: var(--accent-default);
}

Common mistakes

  • Binding UI to primitives. It looks fine until dark mode, when every component needs a manual override.
  • Skipping the semantic layer because the file is small. Small files are exactly where the habit is cheap to build.
  • One mode per collection. Figma collections can hold multiple modes; duplicating collections doubles maintenance.
  • Using Tailwind's numeric shades as semantic names. neutral-700 describes a value, not a role, and means something different in every file.
  • Forgetting scopes. If primitives appear in every picker, designers will use them.

Migration checklist

  • Palette values exported from the Tailwind version the app uses, not from memory.
  • Primitives collection with one variable per swatch and all scopes disabled.
  • Semantic collection with Light and Dark modes, every value an alias.
  • Component-level tokens only where a pattern repeats across files.
  • A documented naming convention for Figma to CSS.
  • One export direction, automated, with the token file in version control.

Skip the manual setup

Generate this structure in one click

Huekit creates the color scales, semantic tokens, and Light/Dark modes inside Figma, and exports JSON tokens or a Tailwind config when you need to hand off.

Keep reading