设计 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 导出至 CursorFigma 导出至 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 排版 无单位数字 4001.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.0color.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 不包含什么

了解范围限制有助于设定正确预期:

_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 对此进行规范化:

音译步骤对国际团队尤为重要。使用西里尔文变量名(如 Фон,意为"背景")的乌克兰设计团队,在输出中会得到稳定的 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 覆盖率的技巧。