Every figmascope export includes tokens.json. It's the bridge between Figma's visual values and the typed constants your code needs. This post covers the schema, how keys are named, what happens when a Figma file has no Variables, and where the token system honestly falls short.
The schema
The top-level structure has four sections:
{
"spacing": {
"spacing.4": { "$value": 4, "$type": "dimension" },
"spacing.8": { "$value": 8, "$type": "dimension" },
"spacing.12": { "$value": 12, "$type": "dimension" },
"spacing.16": { "$value": 16, "$type": "dimension" },
"spacing.24": { "$value": 24, "$type": "dimension" }
},
"radius": {
"radius.4": { "$value": 4, "$type": "dimension" },
"radius.8": { "$value": 8, "$type": "dimension" },
"radius.12": { "$value": 12, "$type": "dimension" }
},
"color": {
"color.7f5cfe": { "$value": "#7f5cfe", "$type": "color" },
"color.ffffff": { "$value": "#ffffff", "$type": "color" },
"color.1a1a2e": { "$value": "#1a1a2e", "$type": "color" }
},
"typography": {}
}
The format is W3C Design Tokens Community Group-ish: each token is an object with $value and $type. It's not a strict W3C DTCG implementation — figmascope predates final spec publication and doesn't implement composite types like fontFamily — but it's close enough that DTCG-aware tooling can parse it with minor adaptation.
The empty typography object is not a bug. It's covered below.
Value-derived key naming
When a Figma file has Variables, token keys come from the Variable names the designer set. spacing.md, color.brand.primary, whatever the design system uses.
When a Figma file has no Variables — which is the majority of real-world Figma files — figmascope falls back to value-derived naming. A spacing value of 16 becomes spacing.16. A color #7f5cfe becomes color.7f5cfe. A corner radius of 4 becomes radius.4.
This is a deliberate tradeoff. Value-derived names are ugly but stable. They're derived from the actual value, so two different runs of the same Figma file produce the same key. spacing.16 always means 16dp. The agent can rely on it.
The alternative would be positional names like spacing.1, spacing.2 etc. Those are brittle — add a smaller spacing value and everything shifts. Value-derived names don't shift.
Value-derived names are a fallback for files that should have Variables but don't. If your design system has 40 spacing values that all need semantic names, push the designer to set up Variables. The inference fallback exists for real-world files, not as a substitute for a real token system. You can run figmascope on your own file to see which sourcing path it takes.
The tokensSource field and what it means
_meta.json includes a tokensSource field that tells you how the tokens were derived:
| Value | Meaning |
|---|---|
"figma-variables" |
The Figma file has Variables and they were used directly. Token names are designer-assigned. Full coverage. |
"inferred-from-frequency" |
No Variables. figmascope scanned all nodes, found frequently-recurring values, promoted them to tokens. Coverage depends on consistency of the design. |
"none" |
No Variables and inference yielded nothing useful. tokens.json will have empty or near-empty sections. |
The "tokens-inferred-from-frequency" warning in _meta.json mirrors this. If you see it, the token coverage is best-effort.
When tokensSource is "inferred-from-frequency", the inference algorithm is: find all dimension values that appear in three or more nodes' padding, gap, or cornerRadius fields. Promote those to spacing or radius tokens respectively. Do the same for fill colors. Values that appear only once or twice are treated as one-off, not promoted.
This works well for designs that are internally consistent. It works poorly for exploratory designs where spacing varies freely. The _meta.json warnings exist precisely so the agent knows which situation it's in.
Why typography is often empty
Typography tokens require Figma Variables with type FLOAT or STRING to be reliably extracted. Text styles exist in Figma as shared styles, not Variables, and the API surface for styles is different from the Variables API.
figmascope v0.4 extracts typography when Variables cover it. It does not attempt frequency-based inference for typography because the useful typography tokens — font family, line height, letter spacing, weight combos — don't have obvious value-derived names the way spacing.16 does. A fontSize.14 key is much less useful than typography.body.small, and generating a bad name is worse than generating no name.
So the result is honest: if your Figma file has typography Variables, you get typography tokens. If not, you get an empty object and the agent is told via CONTEXT.md that typography coverage may be partial.
// _meta.json
{
"tokensSource": "inferred-from-frequency",
"warnings": [
"tokens-inferred-from-frequency"
]
}
The agent sees this and knows to be conservative about typography token references. It generates fallback code with explicit values and a TODO comment rather than inventing a token name.
How the agent uses tokens.json
The CONTEXT.md constraint is: "Never hardcode dp values if a token exists within ±2dp." The ±2dp tolerance handles rounding. If a node has paddingLeft: 15 and spacing.16 exists, the agent uses spacing.16. If the closest token is spacing.24, no match — the agent uses the literal value.
For colors, matching is exact on the hex value after normalization to 6-digit lowercase. #7F5CFE matches color.7f5cfe.
For corner radius, same ±2dp rule as spacing.
The practical output for a Jetpack Compose target looks like this:
// With figma-variables tokensSource
Surface(
shape = RoundedCornerShape(Radius.radius8),
color = Color.Brand.Primary
) {
Column(
verticalArrangement = Arrangement.spacedBy(Spacing.spacing16),
modifier = Modifier.padding(horizontal = Spacing.spacing24)
) { ... }
}
// With inferred-from-frequency tokensSource (same visual output, same token refs)
Surface(
shape = RoundedCornerShape(Radius.radius8),
color = Color.color7f5cfe
) { ... }
The value-derived names are less readable. They're still better than hardcoded values.
Comparison with other token extraction approaches
Figma's native "Inspect" panel shows values per-node. There's no exported token file. You'd have to manually create one or use a plugin like Tokens Studio. Both require designer effort and ongoing maintenance.
figmascope extracts tokens automatically on every export. If the file changes, re-export and the tokens reflect the current state. The tradeoff is that without Variables, the names are value-derived rather than semantic — but you get a token file every time, without a plugin or extra workflow step.
Tokens Studio (formerly Style Dictionary integration) requires the designer to set up the plugin, maintain a JSON file in the Figma file, and sync it. That's the right solution for a mature design system. figmascope's approach is the right solution for files that aren't there yet, which is most files.
The token extraction is a best-effort snapshot of what exists in the file. It's not a substitute for a token-first design system. It's the thing you use while building toward one.
Wiring tokens.json to your codebase
The JSON structure is flat enough that generating a Kotlin object or TypeScript module from it is straightforward. A simple script:
// tokens.json → Kotlin object (simplified)
const tokens = JSON.parse(fs.readFileSync('tokens.json', 'utf-8'));
let output = 'object Spacing {\n';
for (const [key, token] of Object.entries(tokens.spacing)) {
const name = key.replace('spacing.', 'spacing').replace('.', '_');
output += ` val ${name} = ${token.$value}.dp\n`;
}
output += '}';
// → val spacing16 = 16.dp
If you're using a design token pipeline already, the W3C-ish format is close enough to drop into Style Dictionary with a custom transformer for the $value/$type keys.
The rest of how tokens interact with the IR is covered in Per-Screen IR. For how tokens interact with the broader agent context, see Anatomy of CONTEXT.md. For the design token export workflow end to end, see Design Token Export. To export tokens from your own Figma file, head to figmascope.dev.