设计 token 是设计师与开发者之间的共同语汇。它们以各种形式存在已久——从 Salesforce Lightning 时代的 Style Dictionary、Theo,到 W3C Design Token Community Group 的规范草案。近期的变化是:它们同时也成了你的代码库与 AI 编码 agent 之间的共同语汇。
知道 color.accent = #d96a3a 的 agent 会使用这个值。得到截图的 agent 会猜"暖橙色",产出一个相近但错误的结果。这种误差在几十个组件之间不断累积。
figmascope 在每个上下文 bundle 中都会导出一份 tokens.json。本文详细解释其格式、figmascope 从 Figma 获取 token 的方式、针对没有 Figma Variables 的文件的频率推断回退机制,以及如何将输出接入常见框架。要了解 token 如何融入完整导出工作流,请访问 figmascope 应用或阅读 Figma 导出至 Cursor 和 Figma 导出至 Claude Code。
tokens.json 格式
figmascope 使用受 W3C Design Token Community Group 启发的格式。每个 token 都是一个包含 $value 和 $type 字段的对象,嵌套在语义类别键下:
{
"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" }
}
}
}
为什么是"W3C 风格"而非完全遵循规范?
W3C Design Token Community Group 规范仍是草案。figmascope 遵循核心约定($value、$type、嵌套分组),但不实现所有边缘情况,如复合类型和别名解析链。输出足够稳定,可被 Style Dictionary、直接 JSON 遍历或语言模型使用——后者是主要消费方。
使用中的 token 类型
| $type | 类别 | $value 格式 | 示例 |
|---|---|---|---|
color |
颜色 | 十六进制字符串 | "#d96a3a" |
dimension |
间距、圆角 | 带单位的 CSS 字符串 | "16px" |
fontFamily |
排版 | 字体名称字符串 | "Inter" |
number |
排版 | 无单位数字 | 400、1.45 |
颜色始终以十六进制字符串输出。如果 Figma 源文件使用了非完全不透明度的 RGBA,alpha 通道将以 8 位十六进制保留(#d96a3a80)。
figmascope 如何获取 token
figmascope 采用两级获取策略。Figma Variables 是首选来源;频率推断是回退方案。
第一级:Figma Variables
如果 Figma 文件定义了 Variables(Figma 的原生 token 系统,在 Professional 和 Organization 计划中可用),figmascope 通过 REST API 的 /v1/files/:key/variables/local 端点读取它们。变量名用作 token 键,规范化为 kebab-case。模式折叠为默认模式,除非文件只有一个模式,此时直接使用该模式的值。
变量集合映射到 tokens.json 中的顶级类别键。名为"Color"的集合生成 tokens.color.*;"Spacing"生成 tokens.spacing.*。如果你的 Figma 文件使用非标准集合名称,figmascope 会尝试从变量的解析类型推断类别。
第二级:频率推断
许多 Figma 文件——尤其是较早的文件,或来自尚未采用 Variables 的客户——没有变量定义。figmascope 通过遍历整个节点树,构建填充颜色、间距值、圆角半径和排版属性的频率直方图来处理这种情况。
出现频率超过阈值的值成为候选 token。它们按类别和顺序索引命名(color.0、color.1……),除非能从使用方式推断出可读名称(例如,在多个帧中仅用于背景的颜色会变成 color.surface)。
_meta.json 清单将 tokensSource 记录为 "variables" 或 "inferred",这样你的 agent 提示词就能在使用推断时注明:
// _meta.json — inferred fallback
{
"tokensSource": "inferred",
"warnings": [
{
"code": "tokens-inferred",
"message": "No Figma Variables found. Tokens were inferred from usage frequency."
}
]
}
推断出的 token 有一定参考价值,但不具权威性。将其视为起点,而非设计系统规范。
布局 IR 中的 token 引用
逐屏 IR 文件(screens/*.json)使用 $ref 字符串按路径引用 token,而不是嵌入原始值。这使 IR 保持紧凑,并确保 agent 始终从单一真实来源解析值:
{
"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" }
}
]
}
处理这个节点的 agent 从并行的 tokens.json 文件中将 color.accent 解析为 #d96a3a,将 spacing.2 解析为 8px。没有歧义,没有值幻觉。
完整节点 schema 文档请参阅 逐屏 IR 详解。
将 tokens.json 与 Style Dictionary 配合使用
figmascope 的输出与 Style Dictionary 兼容,配置极少。由于它已经使用了 $value / $type 约定,你可以直接将 Style Dictionary 指向 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' }]
}
}
};
运行 style-dictionary build 将从 agent 使用的同一源文件生成 CSS 自定义属性和 ES 模块导出。
将 tokens.json 与 Tailwind 配合使用
一个小脚本可将 tokens.json 转换为 Tailwind theme extension。对于常见情况,结构已足够扁平,无需递归遍历:
// 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
然后在 tailwind.config.ts 中:
import extend from './design/tailwind-extend.json';
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: { extend },
};
将 tokens.json 与 Jetpack Compose 配合使用
对于 Android 项目,token 结构可以简洁地映射到一个 Kotlin object。你可以编程生成,也可以让 Claude Code 来完成。以颜色 token 为例:
// 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
}
}
包含 MaterialTheme 集成的完整指南请参阅 Jetpack Compose from Figma。
tokens.json 不包含什么
了解范围限制有助于设定正确预期:
- 阴影 — figmascope 目前不导出投影或内阴影 token。阴影值在 IR 中以内联形式出现在节点上(如有)。
- 渐变 — 渐变填充在 IR 的叶节点上按原样导出,不作为命名 token。
- 动画 — 时序、缓动和过渡值超出范围。Figma 的动画支持在原型模式中,figmascope 不读取该模式。
- 断点 — 响应式约束在 Figma 变量级别不是概念;它们需要独立的设计决策。
_meta.json 中的 warnings 数组会记录所有无法干净导出的值。在将 bundle 交给 agent 之前先检查它。
token 名称如何派生
当 figmascope 读取 Figma Variables 时,变量在 Figma 中的名称会成为 token 键。在名为"Color"的集合中,名为 Colors/Surface/Primary 的变量在 tokens.json 中变为 tokens.color.surface.primary——Figma 中的路径分隔符是 /,JSON 嵌套键中是 .。
Figma 允许变量名包含空格、大写字母和特殊字符。figmascope 对此进行规范化:
- 空格变为连字符:
Primary Surface→primary-surface - 大写字母转为小写:
AccentOrange→accent-orange - 控制字符和 Unicode 双向标记通过
sanitizeName剥离 - 西里尔文和其他非拉丁字母为兼容 slug 进行音译
音译步骤对国际团队尤为重要。使用西里尔文变量名(如 Фон,意为"背景")的乌克兰设计团队,在输出中会得到稳定的 ASCII 键(fon),可在 CSS 类名和 JSON 中无编码问题地使用。如果 Figma Variable 元数据中存在原始名称,会以 $description 字段保留。
代码生成前检查 token 覆盖率
在将 bundle 交给 agent 之前,验证 token 覆盖率是否合理很有价值。以下快速 Node 脚本可检查屏幕 IR 中引用的所有颜色值是否都能在 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.`);
零缺失引用意味着 agent 能够解析布局 IR 中的每个 $ref,而无需凭空捏造值。如果存在缺失引用,通常意味着 Figma 文件在 bundle 导出后发生了变更——重新运行 figmascope 获取新的导出结果即可。你也可以查看 figmascope 博客获取更多维护跨设计迭代 token 覆盖率的技巧。