设计 Token 系统建设从颜色变量到设计决策的工程化体系一、Token 不是变量从样式复用到设计决策的抽象设计 Token 常被误解为CSS 变量的另一种写法。但 Token 的本质是设计决策的抽象——--color-primary不是蓝色而是品牌主色蓝色只是当前的决策值。当品牌升级时只需修改 Token 的值所有引用该 Token 的组件自动更新。这种抽象层级比 CSS 变量更高——CSS 变量是技术实现Token 是设计语义。Token 系统的层级结构全局 TokenGlobal Token定义原始值如--gray-900: #1a1a2e别名 TokenAlias Token定义语义如--color-text-primary: var(--gray-900)组件 TokenComponent Token定义组件级样式如--button-bg: var(--color-primary)。三级结构确保了修改的影响范围可控。二、Token 系统架构三级抽象与主题切换Token 系统的核心是三级抽象Global → Alias → Component。Global Token 是最底层的原始值不包含语义Alias Token 赋予语义是设计系统的核心Component Token 将 Alias Token 绑定到具体组件实现组件级定制。flowchart TB subgraph Global Token A1[--gray-900: #1a1a2e] A2[--blue-500: #3b82f6] A3[--spacing-4: 16px] A4[--radius-md: 8px] end subgraph Alias Token B1[--color-text-primary: var(--gray-900)] B2[--color-primary: var(--blue-500)] B3[--spacing-md: var(--spacing-4)] B4[--radius-default: var(--radius-md)] end subgraph Component Token C1[--button-bg: var(--color-primary)] C2[--button-padding: var(--spacing-md)] C3[--button-radius: var(--radius-default)] C4[--button-text: var(--color-text-on-primary)] end A1 -- B1 A2 -- B2 A3 -- B3 A4 -- B4 B2 -- C1 B3 -- C2 B4 -- C3 B1 -- C4 subgraph 主题切换 D[Light Theme] E[Dark Theme] end D -- B1 E -- B1主题切换的实现原理不同主题覆盖 Alias Token 的值。Light 主题下--color-bg指向--whiteDark 主题下指向--gray-900。Global Token 不变Alias Token 随主题切换Component Token 自动跟随。三、生产级代码实现Token 定义、主题系统与组件绑定3.1 Token 定义文件/* Global Token: 原始值不包含语义 为什么分离 Global 和 AliasGlobal Token 是设计系统的调色板修改它影响全局 Alias Token 是语义层修改它只影响 特定语义场景。分离后修改更安全 */ :root { /* 颜色 - 灰度 */ --gray-50: #f8f9fa; --gray-100: #f1f3f5; --gray-200: #e9ecef; --gray-300: #dee2e6; --gray-400: #ced4da; --gray-500: #adb5bd; --gray-600: #868e96; --gray-700: #495057; --gray-800: #343a40; --gray-900: #212529; /* 颜色 - 品牌色 */ --blue-50: #e7f5ff; --blue-100: #d0ebff; --blue-200: #a5d8ff; --blue-300: #74c0fc; --blue-400: #4dabf7; --blue-500: #339af0; --blue-600: #228be6; --blue-700: #1c7ed6; --blue-800: #1971c2; --blue-900: #1864ab; /* 间距 */ --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px; /* 圆角 */ --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 16px; --radius-full: 9999px; /* 阴影 */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07); --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); /* 字体 */ --font-sans: Inter, system-ui, -apple-system, sans-serif; --font-mono: JetBrains Mono, Fira Code, monospace; /* 字号 */ --text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; --text-lg: 1.125rem; --text-xl: 1.25rem; --text-2xl: 1.5rem; /* 行高 */ --leading-tight: 1.25; --leading-normal: 1.5; --leading-relaxed: 1.75; }3.2 Alias Token 与主题/* Alias Token: 语义层主题切换的锚点 */ /* Light 主题默认 */ :root, [data-themelight] { /* 语义色 */ --color-bg: var(--gray-50); --color-bg-elevated: var(--white); --color-bg-sunken: var(--gray-100); --color-text-primary: var(--gray-900); --color-text-secondary: var(--gray-600); --color-text-tertiary: var(--gray-400); --color-text-on-primary: var(--white); --color-primary: var(--blue-600); --color-primary-hover: var(--blue-700); --color-primary-active: var(--blue-800); --color-primary-subtle: var(--blue-50); --color-border: var(--gray-300); --color-border-focus: var(--blue-500); /* 语义间距 */ --spacing-inline-xs: var(--space-1); --spacing-inline-sm: var(--space-2); --spacing-inline-md: var(--space-4); --spacing-inline-lg: var(--space-6); --spacing-stack-xs: var(--space-1); --spacing-stack-sm: var(--space-2); --spacing-stack-md: var(--space-4); --spacing-stack-lg: var(--space-6); } /* Dark 主题 */ /* 为什么 Dark 主题只覆盖 Alias Token Global Token 是原始值跨主题不变 只覆盖 Alias Token 确保修改范围可控 且组件 Token 自动跟随 Alias Token 变化 */ [data-themedark] { --color-bg: var(--gray-900); --color-bg-elevated: var(--gray-800); --color-bg-sunken: var(--gray-950); --color-text-primary: var(--gray-50); --color-text-secondary: var(--gray-400); --color-text-tertiary: var(--gray-600); --color-text-on-primary: var(--gray-900); --color-primary: var(--blue-400); --color-primary-hover: var(--blue-300); --color-primary-active: var(--blue-200); --color-primary-subtle: var(--blue-900); --color-border: var(--gray-700); --color-border-focus: var(--blue-400); }3.3 Component Token 与组件实现/* Component Token: 组件级样式绑定 */ /* Button 组件 Token */ .button { /* 组件 Token 定义 */ --button-bg: var(--color-primary); --button-bg-hover: var(--color-primary-hover); --button-bg-active: var(--color-primary-active); --button-text: var(--color-text-on-primary); --button-padding-x: var(--spacing-inline-md); --button-padding-y: var(--spacing-stack-sm); --button-radius: var(--radius-md); --button-font: var(--font-sans); --button-font-size: var(--text-sm); --button-font-weight: 500; /* 使用 Component Token */ background: var(--button-bg); color: var(--button-text); padding: var(--button-padding-y) var(--button-padding-x); border-radius: var(--button-radius); font-family: var(--button-font); font-size: var(--button-font-size); font-weight: var(--button-font-weight); border: none; cursor: pointer; transition: background 0.15s ease; } .button:hover { background: var(--button-bg-hover); } .button:active { background: var(--button-bg-active); } /* 为什么用 Component Token 而非直接用 Alias Token 直接用 Alias Token 时修改 Alias Token 会 影响所有引用它的组件Component Token 允许 单个组件覆盖样式而不影响其他组件 */ .button--secondary { --button-bg: transparent; --button-bg-hover: var(--color-primary-subtle); --button-text: var(--color-primary); --button-border: var(--color-primary); }3.4 Token 管理工具// Token 管理器校验、转换和同步 class DesignTokenManager { constructor(tokens) { this.tokens tokens; } // 校验 Token 的完整性 validate() { const errors []; // 检查所有 Alias Token 是否引用了存在的 Global Token // 为什么需要校验Token 引用链断裂会导致 // 样式失效且难以排查CSS 不会报错 // 只是回退到默认值 for (const [name, value] of Object.entries(this.tokens.alias)) { if (typeof value string value.startsWith(var()) { const ref value.match(/var\(([^,)])\)/)?.[1]; if (ref !this.tokens.global[ref] !this.tokens.alias[ref]) { errors.push(Alias Token ${name} 引用了不存在的 Token ${ref}); } } } return errors; } // 生成 CSS 变量声明 toCSS(theme light) { const lines []; lines.push(:root,); // Global Token for (const [name, value] of Object.entries(this.tokens.global)) { lines.push( ${name}: ${value};); } // Alias Token按主题 lines.push([data-theme${theme}] {); for (const [name, value] of Object.entries(this.tokens.alias[theme])) { lines.push( ${name}: ${value};); } lines.push(}); return lines.join(\n); } // 生成 Tailwind 配置 toTailwindConfig() { // 为什么支持 Tailwind 输出Tailwind 的 // 配置与 Token 系统对齐避免两套体系 return { colors: this._extractColors(), spacing: this._extractSpacing(), borderRadius: this._extractRadius(), fontSize: this._extractFontSizes(), }; } }四、Token 系统的架构权衡粒度、命名与同步成本Token 粒度的权衡Token 越细定制能力越强但维护成本越高。一个 Button 组件可以有 20 个 Component Token背景、文字、边框、阴影、圆角、内边距……也可以只有 3 个variant、size、state。建议核心组件Button、Input、Card用细粒度 Token辅助组件用粗粒度 Token。命名规范的一致性Token 命名必须遵循统一的规则否则团队无法快速理解 Token 的含义。推荐格式{类别}-{属性}-{变体}-{状态}如--color-primary-hover。避免使用具体颜色名如--blue-500作为 Alias Token。设计工具与代码的同步Figma 中的样式变量和代码中的 Token 需要保持同步。手动同步容易遗漏建议使用 Style Dictionary 或 Tokens Studio 等工具自动转换。同步是 Token 系统最大的运维成本。多平台 Token 的统一WebCSS 变量、iOSSwift 颜色/间距常量、AndroidXML 资源的 Token 格式不同。Style Dictionary 可以从统一的 JSON 源文件生成各平台的 Token 文件是跨平台 Token 管理的标准方案。五、总结设计 Token 系统的核心是三级抽象Global Token 定义原始值Alias Token 赋予语义Component Token 绑定组件。主题切换通过覆盖 Alias Token 实现Component Token 自动跟随。落地时建议先建立颜色和间距的 Token 体系再逐步扩展到排版、阴影和动效。命名规范和设计工具同步是长期运维的关键建议在项目初期就建立自动化流程。