Figma Variables landed in 2023 as the platform's long-overdue answer to design tokens. The feature is powerful: named collections of primitive values — colors, numbers, strings, booleans — that you can bind to any property in any component. Change the variable, every instance updates. Add a dark mode collection, every variable binding swaps automatically.
For AI codegen, Variables are not just useful. They are the mechanism that converts a Figma file from a pixel-perfect mockup into a spec your agent can implement correctly. When a color has a name — color/brand/primary, not #7F5CFE — the agent can map it to a code token, implement dark mode correctly, and produce output that participates in your actual design system.
Here's the problem: most Figma files in active use today don't have Variables set up. figmascope handles both cases. This post explains how.
What Variables actually are
A Figma Variable is a named scalar bound to a collection. Collections organize variables by mode — "Light" and "Dark" are the canonical example. Every variable in a collection can have different values per mode: color/surface/background is #FFFFFF in Light and #0D0D0D in Dark. The binding propagates: every fill that references color/surface/background updates when you switch modes.
Variables can be colors, numbers, strings, or booleans. In practice, the most impactful are colors and numbers — which covers most of the token surface area in a typical design system: color palette, spacing scale, border radii, font sizes, elevation values.
Figma exposes Variables via its REST API as a localVariables collection. Each variable has an ID, a name, a type, and values-per-mode. Component properties that reference variables carry a boundVariables field with the variable ID. This is structured data that travels cleanly through the extraction pipeline.
The happy path: Variables are present
When a Figma file has Variables, figmascope reads them from the API and builds a tokens.json following a W3C Design Tokens Community Group-compatible structure. Each token has a $value and a $type. Color tokens get hex values with optional alpha. Spacing tokens get numeric values with a px unit hint. The token name follows the variable's collection and name path:
{
"color": {
"brand": {
"primary": { "$value": "#7F5CFE", "$type": "color" }
},
"surface": {
"background": { "$value": "#FFFFFF", "$type": "color" }
}
},
"spacing": {
"4": { "$value": 4, "$type": "number" },
"8": { "$value": 8, "$type": "number" },
"16": { "$value": 16, "$type": "number" }
}
}
When the per-screen IR is built, every fill that had a boundVariables reference gets the token name instead of the resolved hex. The node carries:
"fills": [{ "type": "SOLID", "tokenRef": "color/brand/primary" }]
Not #7F5CFE. The token name. The agent reads this and generates background-color: var(--color-brand-primary) or Color.brandPrimary or whatever the target framework's token consumption pattern is. That's the output you want: code that's connected to your design system, not code that will break the moment a designer updates a color.
Semantic naming is the difference between code that ages well and code that drifts. A hex value in source is a liability; a token reference is a contract. Variables are what make Figma files capable of expressing contracts, not just pixels.
The reality: most files don't have Variables
Variables require Figma Professional plan or above. They require a designer who has set them up — which means creating collections, naming variables, and manually binding them to every component property. On a mature, well-maintained design system file this is done. On a startup's product Figma, a freelancer's client file, or any file that predates the Variables feature, it's typically not.
figmascope was designed to be useful for those files too. It degrades gracefully: when Variables are absent, it falls back to frequency-based token inference.
The fallback: inferred-from-frequency
The inference algorithm works like this:
- Walk every leaf node in every exported frame.
- Collect all fill colors, spacing values, and border radii.
- Count occurrences of each unique value.
- Values that appear above a frequency threshold are promoted to inferred tokens.
- Each token gets a value-derived name:
color.7f5cfe,spacing.16,radius.8.
The output tokens.json looks structurally similar to the Variables path, but the names are value-derived rather than semantic:
{
"color": {
"7f5cfe": { "$value": "#7F5CFE", "$type": "color" },
"f6f2ea": { "$value": "#F6F2EA", "$type": "color" }
},
"spacing": {
"16": { "$value": 16, "$type": "number" },
"8": { "$value": 8, "$type": "number" }
}
}
In the IR, nodes that use these values get token references: "tokenRef": "color.7f5cfe". Not hardcoded literals. References — just to inferred tokens rather than named ones.
The agent still generates token-referenced code. var(--color-7f5cfe) isn't as readable as var(--color-brand-primary), but it's still a token — you can find-and-replace it, you can rename it, you can audit its usage. It's a named handle on a value, not a magic number.
The tokensSource field
Every figmascope bundle includes a _meta.json that documents what's in the bundle and how it was produced. The tokensSource field has three possible values:
figma-variables— Variables were present and used. Token names are semantic.inferred-from-frequency— No Variables found. Tokens were inferred from value frequency. Names are value-derived.none— No tokens could be extracted or inferred. The IR uses resolved values directly.
This matters because it tells the consuming agent (and the developer reading the bundle) exactly how much to trust the token names. A figma-variables bundle is source-of-truth for your design system. An inferred-from-frequency bundle is a useful structural scaffold that needs designer naming review before it's canonical. A none bundle is a starting point with hardcoded values that need to be tokenized later.
Honest metadata is underrated. Tools that silently infer without flagging it as inference create false confidence. figmascope surfaces the inference chain explicitly so you know what you're working with.
Why frequency inference is better than nothing
The alternative to frequency inference is outputting resolved literal values everywhere — #7F5CFE in every fill node that uses that color. This produces code that's harder to refactor, harder to audit, and harder to connect to a design system when one is eventually added.
Frequency inference at minimum extracts the set of values the design actually uses. If #7F5CFE appears 47 times across the design, that's a signal: this is a primary color, not an accent. The token name doesn't know that — it's just color.7f5cfe — but the frequency data tells the story. An agent given the inferred tokens can make reasonable guesses about which values are primary and which are one-off.
More practically: frequency inference gives you a tokens.json that's diffable across versions. If you export the same file twice after a designer changed a recurring color, the diff shows the token value changed. Without inference, you'd be chasing down every individual literal change scattered across multiple IR files.
What designers should still do
Frequency inference is a compatibility layer, not a substitute for Variables. The right path is for designers to adopt Variables for all values that participate in a design system: brand colors, neutral scale, spacing scale, border radii, elevation, typography. Once those are in place, the figmascope bundle goes from scaffold-quality tokens to production-quality tokens.
Variables also unlock theming in the bundle: multiple mode values per token. A file with Light/Dark modes produces a tokens.json with per-mode values that feeds directly into CSS custom properties with media query overrides, or platform-specific theme objects. This is impossible to infer from a single design snapshot — it requires explicit designer intent, expressed through Variables.
The upgrade path is incremental. A team can start with inference-quality tokens today, adopt Variables gradually as the design system matures, and get better bundles automatically as they do. The tokensSource field tracks where you are in that progression.
The token pipeline in full
To make it concrete, here's the full resolution order figmascope uses for each fill in the IR:
- Does the node have a
boundVariables.fillsreference? If yes, resolve to the variable name and mode-zero value. Token source:figma-variables. - Is the resolved value present in the inferred frequency tokens (above threshold)? If yes, map to the inferred token name. Token source:
inferred-from-frequency. - Otherwise: use the resolved hex value directly. No token reference. Token source:
none.
Steps are tried in order. The highest-quality source wins. The tokensSource field in _meta.json reflects the dominant path for the bundle as a whole.
This means a partially-Variables'd file — where some components have bindings and others don't — produces a mixed bundle. Named tokens where they exist, inferred tokens where they don't. That's the right behavior: use every scrap of structured information available, fall back gracefully where it's missing, and be honest about which path each value took.
Export your bundle from the figmascope app to see which tokensSource your file produces. Then use the bundle with Claude Code or Cursor for accurate, token-referenced code generation.