Figma Variables with Modes: One Component Set, Two Themes, Cleaner Handoff
Duplicating component libraries per theme doesn't scale. Figma Variables with modes let one component set serve light and dark themes — and give engineers token names instead of raw hex values at handoff.
25 Jul 2025, 13:51 UTC

Maintaining a light-theme component library and a dark-theme component library in parallel is a tax every design team eventually resents. Every button variant, card, and dialog exists twice, and the two copies drift apart the moment anyone edits one of them. Figma Variables with modes offer a way out: keep a single component set, store theme values as variables, and flip a frame's mode to re-theme it. The payoff for engineering is just as real — variable names map directly onto design tokens in code, so handoff stops being a translation exercise.
The problem with duplicated component sets
The traditional approach to multi-theme design in Figma is duplication: a "Light" page and a "Dark" page, or worse, two full libraries. This works until it doesn't. A spacing fix lands in one theme and not the other. A new component ships in light mode only. Reviewers can't tell which copy is canonical. The file grows, and so does the surface area for inconsistency.
Variables attack this at the value level rather than the component level. A variable is a named, reusable value — a color, number, string, or boolean — that you bind to properties like fills and strokes instead of hard-coding a hex value. A mode is an alternate value set within the same variable collection. One collection called "Theme" can hold a Light mode and a Dark mode, where each variable resolves differently per mode.
How modes replace the second library
Once your components reference variables instead of raw values, theming becomes a property of the frame, not of the component. You select a screen or section, change its mode from Light to Dark in the layer settings, and every bound fill, stroke, and text color inside it re-resolves. The component itself never changes — only the values flowing into it do.
This is the key architectural shift: the component library stays single-source, and theme becomes data. New components automatically support both themes as long as they bind to the themed variables. There's nothing to sync because there's only one copy.
A worked example: a themed surface color
A minimal setup to validate the pattern:
- Open the Variables panel (right sidebar, under Local variables, with nothing selected) and create a collection named
theme. - Add a second mode to the collection, so you have
LightandDarkcolumns. - Create a color variable named
color/surface/background. Set its Light value to something like#FFFFFFand its Dark value to something like#101418. - Add a companion variable like
color/text/primarywith contrasting values per mode. - Draw a frame representing a screen, set its fill by clicking the color swatch and choosing the variable from the Libraries tab of the color picker — don't paste the hex. Add a text layer bound to
color/text/primary. - Select the frame and, in the Layer section of the right panel, switch the collection's mode from Light to Dark.
The frame and text should re-color instantly. If they don't, the most common cause is that a fill was set with a raw hex value that merely matches the variable rather than being bound to it — re-pick the color via the variable picker. This test takes ten minutes and is worth doing before committing a real file to the pattern.
Why engineers should care: tokens, not hex values
The handoff benefit shows up in Dev Mode. When a developer inspects an element whose fill is bound to a variable, Dev Mode surfaces the variable name alongside the resolved value. Instead of copying #101418 into a stylesheet — a value with no meaning and no relationship to anything — they see color/surface/background, which maps one-to-one onto a CSS custom property:
:root {
--color-surface-background: #ffffff;
}
[data-theme="dark"] {
--color-surface-background: #101418;
}
.card {
background: var(--color-surface-background);
}Now the design file and the codebase speak the same names. When a designer changes the dark-mode surface value, the diff in code is one token value, not a hunt for every place a hex was pasted. Naming conventions matter enormously here — agree on the slash-delimited structure (e.g., color/<role>/<name>) before scaling, because those names become a contract with code.
One caveat: there is no built-in one-click export from variables to a code token format. Getting values into a token pipeline means using the Figma REST API (the variables endpoints), a community plugin, or a third-party token management tool. Prototype one export path end-to-end on a single variable before betting the architecture on it.
Trade-offs and limits worth knowing up front
- Renaming is breaking. Once code consumes variable names, renaming or restructuring a collection breaks the token contract and can orphan bindings. Treat names as an API: version changes deliberately.
- Plan-tier caps exist. The number of variable collections and modes you can create depends on your Figma plan. A team planning four themes (light, dark, high-contrast, brand variants) should check current documented limits before designing the structure — this is a real architectural constraint, not a footnote.
- Variables don't cover everything. Not every property can be bound to a variable; some styling still lives in styles or manual values. Variables complement styles rather than fully replacing them.
- The feature set moves fast. Variables and Dev Mode have evolved rapidly since 2023. UI labels, limits, and API surface in older tutorials may not match what you see today; verify against current documentation for anything load-bearing.
Where to start
Don't migrate a whole library on day one. Pick one high-traffic component family — buttons or cards — bind its colors to a two-mode variable collection, and run the Dev Mode inspection test with a developer on your team. If the token names show up as expected and the mode toggle behaves, you have a validated pattern and a shared vocabulary. Scale from there, with naming conventions written down first, because the names you choose in week one are the ones your codebase will be quoting a year from now.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.