Listen to this article
Take the story with you. Choose a voice and a pace that suits you.
Or listen in another language / choose a voice
Getting your device voices ready…
The voice follows the displayed language and your browser’s regional preference when available. You can choose another narration voice.
The design tokens vs CSS variables argument surfaces in nearly every design-system thread, usually framed as a choice. Pick a side. Declare a winner. Except they were never competing. They sit at different heights in the same stack — one records a decision, the other carries that decision into a browser. Mix them up and you argue about plumbing when the question was architecture.
Short version: tokens are the source, variables are one output. The rest is the working.
What are design tokens before any code exists
Ask what are design tokens and the cleanest answer is almost boring: a token is a design decision stored as data. Not a hex code retyped into twelve files. A name, a value, and a job. Penpot’s guide describes tokens as the smallest indivisible elements of a design system — colours, type sizes, spacing steps, corner radii — each held once, in a format machines can read.
That format is usually JSON. One token might pair the name color-text-error with a deep red value. The name carries intent. The file carries the value. Neither needs a browser to exist.
Tokens stack in layers. Primitive tokens hold raw values — blue-500, space-4, radius-md — named for what they are. Semantic tokens point at primitives and name the job instead: text-primary, surface-raised, action-colour. Component tokens narrow it once more, pinning a value to a single element, like one button’s background. Figma’s guide to design tokens is blunt about the middle layer being the one teams skip and regret: skip semantics, and a rebrand means hunting down every component that grabbed the raw colour.
The format has a standards home, too: the W3C Design Tokens Community Group maintains the specification transformation tools read, so one token file can land in a codebase, an iOS project, and an Android project without anyone retyping a palette.
Design tokens vs CSS variables, side by side
CSS variables — custom properties, formally — are a browser feature. You declare –color-action-primary inside a stylesheet and call it back with var(). The value lives in the CSS itself. It follows the cascade, inherits down the page tree, and can change live. Flip one class and every variable beneath it re-resolves. No rebuild, no reload. That liveness is their whole reason to exist.
Tokens do none of that. A token file just sits there being true. No cascade. No runtime. No opinion about browsers at all. Penpot’s phrasing is worth keeping: tokens define what the values are and mean; variables determine how they run in the browser.
Read that way, the contrast stops being a contest. Tokens: platform-neutral, stored as data, changed at build time, intent baked into the names. Variables: web-only, stored in stylesheets, changed live, only as meaningful as the names you invent. Neither replaces the other — on the web, variables are how tokens clock in for work.
How the two meet in one file
Picture a rebrand. The primary blue moves. In a token setup, you open one JSON file, change one value, and a build tool — Style Dictionary is the common one — regenerates every output. The stylesheet receives a fresh –color-brand-primary. The app receives its matching constant. Nobody greps a codebase at midnight.
Without the token layer, the same change is a careful search-and-replace across stylesheets. It works — right up until a second platform appears, or a dark mode you maintain rather than admire, or a second brand sharing the same components. That is the moment the missing layer sends its invoice.
How to use design tokens in Figma, briefly
In Figma, tokens live as variables, and learning how to use design tokens in Figma means building two collections instead of one. The first collection holds your primitives: palette steps, the spacing scale, type sizes. The second holds semantics, and each semantic variable points at a primitive rather than holding a raw value.
Modes do the theming. Your text-primary variable points at a near-black grey in the Light column and a pale grey in the Dark column. Switch a frame’s mode and everything bound to that variable recolours at once — no duplicate component set, no dark-mode fork of the file.
Hand the file over and Dev Mode shows the token name when a layer gets inspected: color/action/primary arrives as –color-action-primary, ready to drop into a stylesheet. Small moment, but it is the system paying rent: one name in the design file, the same name in the code.
Which layer do you actually need?
One website, written and styled by you alone? Variables can carry that a long way. I am rebuilding my own site by hand, and a three-colour identity — turquoise, yellow, white — does not need a build pipeline to stay consistent. It needs a short list of named values and the discipline to use them.
Add any one of these and the token layer starts earning its keep: an app beside the website, a dark mode under active maintenance, a rebrand already on the calendar, a second pair of hands in the stylesheets. Tokens are insurance priced in setup time. Buy the policy when the risk shows up. Not before, and definitely not after.
A design tokens for beginners starting kit
Starting from zero, keep the first version embarrassingly small. One brand colour, expanded into a ramp of steps. Ten semantic roles you genuinely use — surface, text, border, action, and their partners. A spacing scale in multiples of four. That is a system. Nulab’s token guide cites figures putting most teams at 50 to 300 tokens — enough to talk anyone down from planning a thousand on day one.
Name roles, not looks. A token called text-secondary survives the rebrand that kills gray-text stone dead. Then let the kit stay unfinished for a while. A token system grows by absorbing real decisions, not by predicting them.
Sources:
