Design tokens are the shared vocabulary between designers and developers. They've existed in various forms since the Salesforce Lightning era — Style Dictionary, Theo, the W3C Design Token Community Group spec draft. What's changed recently is that they're now also the shared vocabulary between your codebase and AI coding agents.
An agent that knows color.accent = #d96a3a will use that value. An agent that gets a screenshot will guess "warm orange" and produce something close but wrong. The difference compounds across dozens of components.
figmascope exports a tokens.json as part of every context bundle. This article explains the format in detail, how figmascope sources tokens from Figma, the frequency-inference fallback for files without Figma Variables, and how to wire the output into common frameworks. To see how tokens fit into the full export workflow, visit the figmascope app or read Figma to Cursor and Figma to Claude Code.
The tokens.json format
figmascope uses a W3C Design Token Community Group-inspired format. Each token is an object with $value and $type fields, nested under semantic category keys:
{
"color": {
"surface": { "$value": "#f6f2ea", "$type": "color" },
"surface-2": { "$value": "#efe9dc", "$type": "color" },
"ink": { "$value": "#1f1d1a", "$type": "color" },
"ink-muted": { "$value": "#4a4641", "$type": "color" },
"accent": { "$value": "#d96a3a", "$type": "color" },
"accent-soft": { "$value": "#f2c7a8", "$type": "color" },
"good": { "$value": "#6a8f5a", "$type": "color" },
"warn": { "$value": "#c89a3a", "$type": "color" },
"bad": { "$value": "#b8553a", "$type": "color" }
},
"spacing": {
"1": { "$value": "4px", "$type": "dimension" },
"2": { "$value": "8px", "$type": "dimension" },
"3": { "$value": "12px", "$type": "dimension" },
"4": { "$value": "16px", "$type": "dimension" },
"6": { "$value": "24px", "$type": "dimension" },
"8": { "$value": "32px", "$type": "dimension" },
"12": { "$value": "48px", "$type": "dimension" },
"16": { "$value": "64px", "$type": "dimension" }
},
"radius": {
"sm": { "$value": "4px", "$type": "dimension" },
"md": { "$value": "8px", "$type": "dimension" },
"lg": { "$value": "16px", "$type": "dimension" },
"full": { "$value": "9999px", "$type": "dimension" }
},
"typography": {
"body": {
"fontFamily": { "$value": "Inter", "$type": "fontFamily" },
"fontSize": { "$value": "14px", "$type": "dimension" },
"fontWeight": { "$value": 400, "$type": "number" },
"lineHeight": { "$value": 1.45, "$type": "number" }
},
"heading": {
"sm": {
"fontFamily": { "$value": "Inter", "$type": "fontFamily" },
"fontSize": { "$value": "18px", "$type": "dimension" },
"fontWeight": { "$value": 600, "$type": "number" },
"lineHeight": { "$value": 1.25, "$type": "number" }
}
},
"mono": {
"fontFamily": { "$value": "JetBrains Mono", "$type": "fontFamily" },
"fontSize": { "$value": "13px", "$type": "dimension" },
"fontWeight": { "$value": 400, "$type": "number" }
}
}
}
Why W3C-ish and not the spec exactly?
The W3C Design Token Community Group spec is still a draft. figmascope follows the core conventions ($value, $type, nested groups) but doesn't implement all edge cases like composite types and alias resolution chains. The output is stable enough to be consumed by Style Dictionary, direct JSON traversal, or a language model — which is the primary consumer.
Token types in use
| $type | Category | $value format | Example |
|---|---|---|---|
color |
color | hex string | "#d96a3a" |
dimension |
spacing, radius | CSS string with unit | "16px" |
fontFamily |
typography | font name string | "Inter" |
number |
typography | unitless number | 400, 1.45 |
Colors are always output as hex strings. If the Figma source uses RGBA with non-full opacity, the alpha channel is preserved in 8-digit hex (#d96a3a80).
How figmascope sources tokens
figmascope uses a two-tier sourcing strategy. Figma Variables are the preferred source; frequency inference is the fallback.
Tier 1: Figma Variables
If the Figma file has Variables defined (Figma's native token system, available on Professional and Organization plans), figmascope reads them via the REST API's /v1/files/:key/variables/local endpoint. Variable names are used as token keys, sanitized to kebab-case. Modes are collapsed to the default mode unless the file has a single mode, in which case that mode's values are used directly.
Variable collections map to the top-level category keys in tokens.json. A collection called "Color" produces tokens.color.*; "Spacing" produces tokens.spacing.*. If your Figma file uses non-standard collection names, figmascope attempts to infer the category from the variable's resolved type.
Tier 2: Frequency inference
Many Figma files — especially older ones or files from clients who haven't adopted Variables — have no variable definitions. figmascope handles this by walking the entire node tree and building frequency histograms of fill colors, spacing values, corner radii, and typography properties.
Values appearing above a frequency threshold become candidate tokens. They're named by their category and a sequential index (color.0, color.1...) unless a human-readable name can be inferred from how the value is used (e.g., a color used only on backgrounds across multiple frames becomes color.surface).
The _meta.json manifest records tokensSource as either "variables" or "inferred", so your agent prompt can note when inference was used:
// _meta.json — inferred fallback
{
"tokensSource": "inferred",
"warnings": [
{
"code": "tokens-inferred",
"message": "No Figma Variables found. Tokens were inferred from usage frequency."
}
]
}
Inferred tokens are useful but not authoritative. Treat them as a starting point, not a design system specification.
Token references in the layout IR
The per-screen IR files (screens/*.json) reference tokens by path using $ref strings rather than embedding raw values. This keeps the IR compact and ensures the agent always resolves values from a single source of truth:
{
"kind": "stack",
"name": "PrimaryButton",
"direction": "horizontal",
"gap": { "$ref": "spacing.2" },
"padding": {
"top": 10, "right": 16, "bottom": 10, "left": 16
},
"background": { "$ref": "color.accent" },
"radius": { "$ref": "radius.sm" },
"children": [
{
"kind": "leaf",
"name": "ButtonLabel",
"type": "text",
"style": { "$ref": "typography.body" },
"color": { "$ref": "color.surface" }
}
]
}
An agent processing this node resolves color.accent to #d96a3a and spacing.2 to 8px from the parallel tokens.json file. No ambiguity, no hallucination of values.
See per-screen IR explained for the full node schema documentation.
Using tokens.json with Style Dictionary
figmascope's output is compatible with Style Dictionary with minimal configuration. Since it already uses the $value / $type convention, you can point Style Dictionary directly at tokens.json:
// style-dictionary.config.js
module.exports = {
source: ['design/tokens.json'],
platforms: {
css: {
transformGroup: 'css',
prefix: 'fs',
buildPath: 'src/styles/',
files: [{ destination: 'tokens.css', format: 'css/variables' }]
},
js: {
transformGroup: 'js',
buildPath: 'src/',
files: [{ destination: 'tokens.js', format: 'javascript/es6' }]
}
}
};
Running style-dictionary build will produce CSS custom properties and ES module exports from the same source file the agent uses.
Using tokens.json with Tailwind
A small script converts tokens.json to a Tailwind theme extension. The structure is flat enough to traverse without recursion for the common cases:
// scripts/tokens-to-tailwind.js
const fs = require('fs');
const tokens = JSON.parse(fs.readFileSync('design/tokens.json', 'utf8'));
function flattenGroup(group) {
return Object.fromEntries(
Object.entries(group).map(([k, v]) => [k, v.$value])
);
}
const extend = {
colors: flattenGroup(tokens.color),
spacing: flattenGroup(tokens.spacing),
borderRadius: flattenGroup(tokens.radius),
};
console.log(JSON.stringify(extend, null, 2));
node scripts/tokens-to-tailwind.js > design/tailwind-extend.json
Then in tailwind.config.ts:
import extend from './design/tailwind-extend.json';
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: { extend },
};
Using tokens.json with Jetpack Compose
For Android projects, the token structure maps cleanly to a Kotlin object. You can generate it programmatically or ask Claude Code to do so. For color tokens:
// Generated from design/tokens.json
object DesignTokens {
object Color {
val surface = Color(0xFFF6F2EA)
val ink = Color(0xFF1F1D1A)
val accent = Color(0xFFD96A3A)
val accentSoft = Color(0xFFF2C7A8)
}
object Spacing {
val s1 = 4.dp
val s2 = 8.dp
val s4 = 16.dp
val s8 = 32.dp
}
}
See Jetpack Compose from Figma for a complete guide including MaterialTheme integration.
What tokens.json does not contain
Knowing the scope limits helps set correct expectations:
- Shadows — figmascope does not currently export drop-shadow or inner-shadow tokens. Shadow values appear inline on nodes in the IR where present.
- Gradients — gradient fills are exported as-is on leaf nodes in the IR, not as named tokens.
- Animation — timing, easing, and transition values are outside scope. Figma's animation support is in Prototype mode, which figmascope does not read.
- Breakpoints — responsive constraints are not a Figma concept at the variable level; they require separate design decisions.
The warnings array in _meta.json will note any values that couldn't be exported cleanly. Review it before handing the bundle to an agent.
How token names are derived
When figmascope reads Figma Variables, the variable's name in Figma becomes the token key. A variable named Colors/Surface/Primary in a collection named Color becomes tokens.color.surface.primary in tokens.json — the path separator is / in Figma, . in JSON nested keys.
Figma allows variable names with spaces, uppercase letters, and special characters. figmascope normalizes these:
- Spaces become hyphens:
Primary Surface→primary-surface - Uppercase is lowercased:
AccentOrange→accent-orange - Control characters and Unicode bidi marks are stripped via
sanitizeName - Cyrillic and other non-Latin scripts are transliterated for slug compatibility
The transliteration step matters for international teams. A Ukrainian design team using Cyrillic variable names like Фон (background) gets a stable ASCII key (fon) in the output, which is usable in CSS class names and JSON without encoding issues. The original name is preserved as a $description field if present in the Figma Variable metadata.
Checking token coverage before codegen
Before handing the bundle to an agent, it's worth verifying that the token coverage looks reasonable. A quick Node script that checks whether all color values referenced in the screen IRs resolve to entries in tokens.json:
// scripts/check-token-coverage.js
const fs = require('fs');
const glob = require('glob');
const tokens = JSON.parse(fs.readFileSync('design/tokens.json', 'utf8'));
const screenFiles = glob.sync('design/screens/*.json');
function getRef(obj) {
const refs = [];
if (obj && typeof obj === 'object') {
if (obj.$ref) refs.push(obj.$ref);
for (const v of Object.values(obj)) refs.push(...getRef(v));
}
return refs;
}
function resolveRef(ref, tokens) {
return ref.split('.').reduce((o, k) => o?.[k], tokens);
}
let missing = 0;
for (const file of screenFiles) {
const screen = JSON.parse(fs.readFileSync(file, 'utf8'));
for (const ref of getRef(screen)) {
if (!resolveRef(ref, tokens)) {
console.error(`Missing token: ${ref} (in ${file})`);
missing++;
}
}
}
if (missing === 0) console.log('All token refs resolve.');
else console.error(`${missing} unresolved token refs.`);
Zero missing refs means the agent will be able to resolve every $ref in the layout IR without making up values. If there are missing refs, it usually means the Figma file changed after the bundle was exported — re-run figmascope to get a fresh export. You can also check the figmascope blog for more tips on maintaining token coverage across design iterations.