Design Tokens in 2026: From Figma Variables to CSS Custom Properties
Design tokens bridge Figma variables and CSS custom properties — here's how to build a token pipeline that actually survives a design system at scale.
What Design Tokens Actually Are (And Why People Still Get This Wrong)
Design tokens are named values that represent design decisions — spacing, color, typography, motion. That's it. They're not magic. They're not a framework. They're a contract between designers and engineers that says "this 8px gap has a name, and that name is the same on both sides of the handoff."
Honestly, the confusion comes from years of teams treating tokens like a CSS variable dump. You'd see --color-blue-500: #3B82F6 and call it a day. That's not a token — that's a raw value with a label slapped on it. A real token carries semantic meaning: --color-interactive-primary tells you *why* that blue exists, not just what it is.
In 2026, with Figma Variables hitting their stride and the W3C Design Token Community Group format becoming the de facto JSON schema, this distinction matters more than ever. If you're still building a design system without a formal token layer, you're going to feel the pain the moment a brand refresh lands or a dark mode request comes in.
Worth noting: Empire UI is built on exactly this kind of semantic token architecture — every component references purpose-driven tokens, not raw hex values.
The Figma Variables Side of the Equation
Figma Variables shipped with a primitive/semantic split baked in. Primitives are your raw palette — Blue/500, Neutral/100. Semantics sit on top: Color/Interactive/Default references Blue/500. This maps cleanly to how you should structure tokens in code.
Here's what a clean Figma Variables export looks like when run through a token transformer (we'll get to tooling shortly):
{
"color": {
"interactive": {
"default": { "$value": "{color.blue.500}", "$type": "color" },
"hover": { "$value": "{color.blue.600}", "$type": "color" },
"disabled": { "$value": "{color.neutral.300}", "$type": "color" }
}
}
}The $value and $type fields follow the W3C DTCG spec. That matters because tools like Style Dictionary 4.x, Cobalt, and Tokens Studio all consume this format natively. You don't want a bespoke parser that breaks every six months.
Quick aside: Figma's REST API lets you pull variable collections directly. If you're on a team where designers push to a shared library, you can automate token extraction as part of your CI pipeline. No more "did you update the tokens?" Slack messages.
Transforming Tokens Into CSS Custom Properties
Style Dictionary is still the workhorse here. Version 4 (released in late 2024) rewrote the transform pipeline and added first-class support for the W3C format. You define a config, run it, get CSS out. The output for a color token looks like this:
:root {
/* Primitives */
--color-blue-500: #3B82F6;
--color-blue-600: #2563EB;
--color-neutral-300: #D1D5DB;
/* Semantics */
--color-interactive-default: var(--color-blue-500);
--color-interactive-hover: var(--color-blue-600);
--color-interactive-disabled: var(--color-neutral-300);
}
[data-theme="dark"] {
--color-interactive-default: var(--color-blue-400);
--color-interactive-hover: var(--color-blue-300);
}That [data-theme="dark"] override is the payoff. You swap one attribute on the root element and the entire UI shifts. No JavaScript color logic scattered across 40 components. No conditional class names. The cascade does the work.
In practice, the primitive layer should never be referenced directly in component styles. Your button's background should be var(--color-interactive-default), not var(--color-blue-500). If a designer swaps the brand color from blue to teal in Figma, only your primitives change — every semantic reference follows automatically. That's the contract working as designed.
If you're building on Tailwind CSS, you can feed these CSS custom properties into your tailwind.config.js via theme.extend.colors. You get utility classes that pull from your token layer, which means your design system and your utility-first classes stay synchronized without duplication.
Token Naming: The Part Nobody Talks About Until It's Too Late
Naming is where token systems collapse. Most teams start with color and spacing, ship a v1, and then six months later you've got --spacing-4 meaning 16px in one place and 4px in another because two engineers made different assumptions about your scale unit.
Lock in a naming convention before you write token one. A solid pattern: {category}/{variant}/{state}/{scale}. So color/background/surface/subtle, spacing/component/button/paddingX, typography/body/size/md. Verbose? Yes. Ambiguous? Never.
Look, you don't need to follow this exact schema. But you do need *a* schema, documented, version-controlled, and enforced with a linter. Theo Browne's eslint-plugin-design-tokens can flag raw hex values in component files — set it up early and you won't spend a Friday auditing 200 files for stray #fff strings.
One more thing — scale tokens are their own beast. If you're using an 8px base grid (the right call for most UIs), your spacing scale maps to 4, 8, 12, 16, 24, 32, 48, 64px. Name them xs, sm, md, lg, xl, 2xl, 3xl, 4xl or 100, 200, 300... — but pick one and don't mix them. The glassmorphism generator and box shadow generator on Empire UI both generate output you can drop straight into your token file as shadow tokens.
Composing Token Tiers for Theming and Multi-Brand Systems
Single-brand apps can survive with two tiers: primitives and semantics. Multi-brand systems need three: primitives, semantics, and components. That third tier is where things like --button-background-default live — it references the semantic layer, which references primitives. You can swap an entire brand by changing only the semantic-to-primitive mappings.
Here's what a three-tier component token looks like in React, pulling from CSS custom properties:
// Button.tsx
const buttonStyles = {
background: 'var(--button-background-default)',
color: 'var(--button-text-default)',
padding: 'var(--button-padding-y) var(--button-padding-x)',
borderRadius: 'var(--button-border-radius)',
fontSize: 'var(--button-font-size)',
} as const;
export function Button({ children, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button style={buttonStyles} {...props}>
{children}
</button>
);
}No hardcoded values anywhere in that component. Swap the theme, swap the brand — the component adapts. This is exactly how you'd want to build if you're working with a system like Empire UI where the same component needs to render across wildly different visual styles like glassmorphism or neobrutalism.
That said, don't over-engineer the component token tier. If you're creating a token for every single CSS property on every component, you're building documentation overhead, not a system. Token-ize the properties that actually change across themes or brands. Static values can stay in component styles.
Automating the Token Pipeline in CI
Manual token exports break. Someone forgets to run the build, a PR ships with stale CSS, and now production looks different from staging. Automate it.
The basic pipeline: Tokens Studio (Figma plugin) syncs to a GitHub repo via their GitHub Action. A Style Dictionary build step runs on every push to main. The output CSS gets committed to your design system package. Consuming apps pull from the package. The whole loop takes about 90 seconds.
For teams on a monorepo, you can publish your token output as a workspace package — @yourorg/tokens — and import it in each app. This gives you a single source of truth and the ability to audit exactly which version of the tokens a given app is on. Version your tokens with semver. Breaking naming changes are majors. Additive changes are minors. Value tweaks are patches.
Is this overkill for a side project? Absolutely. But if you've got three engineers, a designer, and stakeholders who like changing brand colors on a quarterly basis, this setup pays for itself in the first month.
What's Changed in 2026 and What's Next
The W3C DTCG spec is close to a final recommendation as of mid-2026. That means the JSON format is stable enough to build on without worrying about churn. Tooling has consolidated around it — Tokens Studio, Style Dictionary 4.x, Cobalt UI, and the new Figma Tokens REST API all speak the same language.
CSS @property has become reliable across all major browsers since 2025. This changes what's possible — you can now animate custom properties with transitions, something you couldn't do with bare CSS variables. Token-driven motion design is becoming a real pattern, not just a theory in design system talks.
Honestly, the frontier right now is spatial tokens for 3D and immersive UIs. With WebGPU adoption accelerating, teams are starting to define depth, parallax offset, and blur radius as tokens — not just color and spacing. It's early, but if you're building anything with layered UI (glass, depth, shadows), it's worth thinking about how your current token architecture extends into that space. Tools on Empire UI like the gradient generator are already generating values that map cleanly to spatial token definitions.
FAQ
A CSS variable is just a named value in your stylesheet. A design token is a named design decision that can be transformed into any output format — CSS variables, iOS Swift constants, Android XML, JSON. Every design token can become a CSS variable, but not every CSS variable is a design token.
For small projects, manual CSS variables are fine. The moment you have a dark mode, multiple themes, or a designer working in Figma who needs to export changes, a build tool like Style Dictionary earns its setup cost immediately.
Figma's primitive and semantic variable collections map directly — primitives become raw token values with a $type, semantics become alias tokens using $value: "{path.to.primitive}" syntax. Tokens Studio handles this export automatically.
Always semantic tokens. If --button-background-default points directly at --color-blue-500, you lose the theming layer — changing the brand color means hunting down every component token that referenced a primitive. Go through semantics every time.