设计令牌与组件架构
跨平台可扩展的令牌管线、命名约定与组件组合策略。
设计令牌进阶
为什么
硬编码颜色和间距会导致主题漂移,使白标无法实现。设计令牌将决策集中为命名值,支持暗色模式、多品牌主题,以及从单一真源实现跨平台一致(Web + iOS + Android)。
Tokens Studio(原 Figma Tokens)有 50 万以上安装量,W3C 设计令牌格式模块即将成为候选推荐。令牌从单一来源实现暗色模式、多品牌主题和平台一致性。
何时使用
当有 2 个以上主题、跨平台发布或需要设计-工程同步且无 Figma 到代码漂移时引入令牌。从颜色 + 间距开始,后续加排版和动效。
如何做
1. 定义令牌分类 — 按原始(color)、语义(color.action.primary)、组件(button.bg.primary)分组
2. 选择格式 — Style Dictionary 多平台输出(CSS 变量、JS、Swift、Kotlin)
3. 编写源 — Figma 中 JSON 或 Tokens Studio,与代码一起在 git 中版本化
4. 构建管线 — style-dictionary build 输出 dist/tokens.css、tokens.ts、tokens.swift
5. 接入组件 — 组件引用 var(--color-action-primary),绝不使用原始 hex
6. 添加主题切换 — 在 <html> 上切换 data-theme 属性加载不同令牌集
7. 校验 — stylelint-declaration-strict-value 强制 CSS 中无硬编码值
示例
// tokens/color.json(Style Dictionary 源)
{
"color": {
"brand": { "primary": { "500": "#6366f1", "600": "#4f46e5" } },
"action": {
"primary": { "value": "{color.brand.primary.500}", "type": "color" },
"hover": { "value": "{color.brand.primary.600}", "type": "color" }
}
}
}
// config.js — Style Dictionary
const StyleDictionary = require("style-dictionary");
StyleDictionary.extend({
source: ["tokens/**/*.json"],
platforms: {
css: { transformGroup: "css", buildPath: "dist/", files: [{
destination: "tokens.css", format: "css/variables"
}]},
ts: { transformGroup: "js", buildPath: "dist/", files: [{
destination: "tokens.ts", format: "javascript/es6"
}]}
}
}).buildAllPlatforms();
/* dist/tokens.css(生成)
:root { --color-action-primary: #6366f1; --color-action-hover: #4f46e5; } */
// Button.css — 引用令牌,绝不使用原始值
.btn-primary { background: var(--color-action-primary); }
.btn-primary:hover { background: var(--color-action-hover); }
常见问题
- ⚠令牌层级过多 — 每个值都有原始 + 语义 + 组件三层,产生 500+ 无人使用的令牌
- ⚠令牌未导出到所有平台 — Web 用 CSS 变量但 iOS 用硬编码颜色,漂移回归
- ⚠重命名令牌破坏所有使用者 — 无 codemod,团队永久保留废弃别名
- ⚠主题覆盖优先级战争 — 满地 !important,因为组件样式压过 :root 令牌
最佳实践
- ✓最多 3 层:原始 → 语义 → 组件,仅在证明复用时才加组件层
- ✓CI 中生成平台输出 — tokens.css 与 tokens.swift 不一致时构建失败
- ✓为令牌重命名写 codemod(jscodeshift)— 在重命名同一 PR 中运行
- ✓主题覆盖放在 :root[data-theme='dark'] — 绝不在组件 CSS 内覆盖
常见问题解答
令牌应该有几层?
最多三层:原始(color.blue.500)→ 语义(color.action.primary)→ 组件(button.bg)。仅在证明复用时加组件层,否则会有 500+ 闲置令牌。
Style Dictionary 和 Tokens Studio 用哪个?
互补:Tokens Studio 在 Figma 编写令牌导出 JSON;Style Dictionary 将 JSON 编译为 CSS 变量、JS、Swift、Kotlin。管线中两者都用。
如何重命名令牌而不破坏使用者?
写 jscodeshift codemod 更新所有引用,在重命名同一 PR 中运行。保留废弃别名一个发布周期后删除。
为什么暗色覆盖需要 !important?
因为组件 CSS 优先级压过 :root 令牌。把覆盖移到文档级 :root[data-theme='dark'],绝不在组件 CSS 内。
如何保持 Web 和 iOS 令牌同步?
CI 中从单一 JSON 源生成两平台输出——tokens.css 与 tokens.swift 不一致时构建失败。绝不手改平台输出。