
Design Token 單一真源從 Figma 變量到代碼的工程化同步一、設計稿與代碼的漂移Token 治理的工程痛點在多人協作的前端工程中設計稿與代碼不一致是高頻出現的協作債務。設計師在 Figma 中定義了一組顏色變量如color/brand/primary-500開發者在代碼中以硬編碼方式如#3B82F6使用。當品牌升級需要調整主色時設計師在 Figma 中改一次開發者卻需要在代碼庫中全局搜索替換遺漏與不一致幾乎不可避免。這種漂移的根因是設計源與代碼源分離。設計稿與代碼各自維護一份顏色、間距、字體的真理兩者之間沒有機器可校驗的同步鏈路。Design Token 的提出正是為了消除這一分裂——它定義了一種與平臺無關的中間表示使設計決策可以從 Figma 單向流向前端、iOS、Android 等多端代碼產物。但 Design Token 落地的工程復雜度遠超把顏色寫成變量。它涉及 Token 的分層策略、命名規范、跨平臺轉譯、版本管理與 CI 校驗。本文聚焦 Figma 到前端代碼的同步鏈路討論生產級 Token 體系的工程實現與權衡。二、Token 分層與同步鏈路從 Figma 變量到多平臺產物要理解 Design Token 的同步鏈路需要先看 Token 的分層模型。W3C Design Tokens Format Module 定義了 Token 的標準結構但實際工程中需要在標準之上做分層治理。2.1 Token 的三層分層模型生產級 Token 體系通常分為三層原始 Token、語義 Token、組件 Token。原始 Token 是無意義的原子值如color-blue-500: #3B82F6。它只描述是什么不描述用于哪里。語義 Token 描述用途如color-background-primary它的值引用原始 Token。組件 Token 描述具體組件的某個屬性如button-primary-bg它的值引用語義 Token。三層之間的引用關系如下圖所示。[Figma Variables] [代碼產物] ------------------ ------------------- | 原始 Token | Style | CSS 變量 | | color-blue-500 | Dictionary | --color-blue-500 | | space-4 | -------------- | --space-4 | ------------------ 轉譯 ------------------- | | v v ------------------ ------------------- | 語義 Token | | CSS 變量語義 | | color-bg-primary | 引用關系保留 | --color-bg-primary| | color-blue-500| | var(--color-blue-500) | ------------------ ------------------- | | v v ------------------ ------------------- | 組件 Token | | 組件級樣式 | | button-bg | | .button { | | color-bg-... | | background: | ------------------ | var(--color-bg-primary)| | } | -------------------2.2 同步鏈路的關鍵節點從 Figma 到代碼的同步鏈路包含五個關鍵節點每個節點都有明確的輸入輸出與校驗職責。節點輸入輸出校驗職責Figma Variables設計師定義.tokens.jsonW3C 格式命名規范、引用完整性Token 倉庫.tokens.jsonStyle Dictionary 配置分層結構、循環引用Style DictionaryToken 加配置CSS、SCSS、TS、iOS、Android轉譯正確性前端代碼庫轉譯產物組件樣式Token 使用率 lintCI 校驗PR diff通過或阻斷禁止硬編碼顏色2.3 引用關系與循環檢測語義 Token 引用原始 Token組件 Token 引用語義 Token形成有向無環圖DAG。Style Dictionary 在轉譯時會展開引用將button-bg: {color-bg-primary}解析為最終的 CSS 值。但如果 Token 之間存在循環引用如 A 引用 BB 又引用 A轉譯會陷入死循環。工程上需要在 Token 入庫階段做拓撲排序校驗發現環則拒絕入庫。三、Style Dictionary 流水線生產級 Token 轉譯與校驗實現以下實現基于 Style Dictionary v4它支持 W3C Design Tokens Format Module并可通過插件擴展多平臺輸出。3.1 Token 文件結構與命名規范// tokens/primitive/color.json // 原始 Token 層只包含無語義的原子值 // 命名規范{category}-{item}-{variant} // 嚴禁在此層引入業務語義否則會破壞分層治理 { color: { blue: { 500: { value: #3B82F6, type: color }, 600: { value: #2563EB, type: color } }, gray: { 100: { value: #F3F4F6, type: color }, 900: { value: #111827, type: color } } }, space: { 4: { value: 16px, type: dimension }, 8: { value: 32px, type: dimension } } }// tokens/semantic/color.json // 語義 Token 層使用引用而非硬編碼 // 引用語法 {path.to.token} 是 W3C 標準的一部分 // 關鍵約束語義 Token 只能引用原始 Token禁止跨語義層引用 { color: { background: { primary: { value: {color.gray.100}, type: color }, inverse: { value: {color.gray.900}, type: color } }, brand: { primary: { value: {color.blue.500}, type: color }, primary-hover:{ value: {color.blue.600}, type: color } } } }3.2 Style Dictionary 配置與多平臺轉譯// style-dictionary.config.mjs // Style Dictionary v4 配置 // 關鍵設計 // 1. 按原始、語義、組件三層分別 include確保引用順序 // 2. 每個平臺web/css、web/ts獨立配置避免產物耦合 // 3. 轉譯時保留引用關系CSS 變量版便于運行時主題切換 import StyleDictionary from style-dictionary; import { promises as fs } from node:fs; import path from node:path; // 自定義格式輸出帶 CSS 變量引用的產物 // 選擇保留引用而非展開最終值是為了支持運行時主題切換 // 展開值會導致主題切換時需要重新加載所有 CSS StyleDictionary.registerFormat({ name: css/variables-with-references, format: async ({ dictionary, file }) { const lines [ /* Generated by Style Dictionary - do not edit */, :root {, ]; for (const token of dictionary.allTokens) { // 原始 Token 輸出值語義 Token 輸出 var() 引用 const value token.original.value.startsWith({) ? var(--${token.path.join(-)}) : token.value; lines.push( --${token.path.join(-)}: ${value};); } lines.push(}); return lines.join(\n); }, }); const sd new StyleDictionary({ // include 順序決定引用解析原始 Token 必須先于語義 Token include: [ tokens/primitive/**/*.json, tokens/semantic/**/*.json, tokens/component/**/*.json, ], platforms: { css: { transformGroup: css, buildPath: dist/css/, files: [ { destination: tokens.css, format: css/variables-with-references, }, ], }, ts: { transformGroup: ts, buildPath: dist/ts/, files: [ { destination: tokens.ts, format: javascript/es6, // TS 產物用于組件庫的類型校驗確保代碼中使用合法 Token options: { type: module }, }, ], }, }, }); // 構建前的循環引用檢測 // 通過拓撲排序判斷 Token 引用圖是否存在環 // 環的存在會導致 Style Dictionary 轉譯時無限遞歸 async function detectCircularReferences(tokens) { const graph new Map(); for (const token of tokens) { const refs extractReferences(token.original.value); graph.set(token.path.join(.), refs); } // 深度優先遍歷檢測環 const visited new Set(); const stack new Set(); for (const [node] of graph) { if (hasCycle(node, graph, visited, stack)) { throw new Error(檢測到循環引用起始節點${node}); } } } function extractReferences(value) { if (typeof value ! string) return []; const matches value.matchAll(/\{([^}])\}/g); return [...matches].map((m) m[1]); } function hasCycle(node, graph, visited, stack) { if (stack.has(node)) return true; if (visited.has(node)) return false; visited.add(node); stack.add(node); for (const dep of graph.get(node) ?? []) { if (hasCycle(dep, graph, visited, stack)) return true; } stack.delete(node); return false; } try { // 先做循環檢測避免 Style Dictionary 進入死循環導致 CI 卡死 await detectCircularReferences(sd.tokens); await sd.cleanAllPlatforms(); await sd.buildAllPlatforms(); console.log([tokens] 轉譯完成); } catch (err) { console.error([tokens] 轉譯失敗${err.message}); process.exit(1); }3.3 CI 校驗與硬編碼阻斷// scripts/lint-tokens-usage.js // 校驗代碼庫中是否出現硬編碼顏色或間距 // 阻斷策略 // - 顏色十六進制值如 #3B82F6直接阻斷 // - px 間距值如 16px記錄警告允許但不推薦 // - 例外tailwind 配置、構建腳本本身可豁免 const { execSync } require(node:child_process); const IGNORE_PATTERNS [ tailwind.config.js, scripts/lint-tokens-usage.js, style-dictionary.config.mjs, ]; // 獲取本次 PR 修改的樣式相關文件 const changedFiles execSync( git diff --name-only --diff-filterACM origin/main...HEAD, { encoding: utf8 } ).split(\n).filter(Boolean); const violations []; for (const file of changedFiles) { if (IGNORE_PATTERNS.some((p) file.includes(p))) continue; if (!/\.(css|scss|vue|tsx|jsx)$/.test(file)) continue; const content execSync(git show HEAD:${file}, { encoding: utf8 }); // 匹配十六進制顏色但不匹配注釋中的說明 const hexColorMatches content.matchAll(/(?!\/\/.*)#([0-9a-fA-F]{3,8})\b/g); for (const match of hexColorMatches) { violations.push({ file, line: content.slice(0, match.index).split(\n).length, value: match[0], }); } } if (violations.length 0) { console.error([lint] 發現硬編碼顏色應使用 Design Token); for (const v of violations) { console.error( - ${v.file}:${v.line} 使用了 ${v.value}); } process.exit(1); } console.log([lint] 通過未發現硬編碼顏色);四、Token 體系的代價治理成本與平臺差異邊界Design Token 體系引入的治理成本與平臺差異需要在落地前充分評估。4.1 治理成本與組織協作Token 體系的引入會改變設計師與開發者的協作模式。設計師需要在 Figma 中嚴格使用 Variables 而非自由填色這要求 Figma 協作規范的培訓成本。開發者需要從隨手寫顏色切換到查 Token 字典初期開發效率會有所下降。根據生產項目的觀測數據接入 Token 體系后的前兩周組件開發耗時平均增加 15% 至 20%但在第三周后回落到原有水平長期看因減少返工而凈收益為正。治理手段是引入 IDE 插件如 VSCode 的 Design Token 自動補全將 Token 查詢的摩擦降到最低。4.2 平臺差異與轉譯損耗不同平臺的樣式系統存在原生差異。CSS 變量是運行時可改的而 iOS 的 UIColor 在編譯期確定Android 的資源系統對命名有約束小寫下劃線。Style Dictionary 的 transformGroup 會做平臺適配但某些復雜 Token如帶透明度的顏色、響應式間距在轉譯到 iOS 時會丟失語義。生產實踐中對復雜 Token 需要為每個平臺單獨定義 transform代價是配置文件膨脹可維護性下降。4.3 版本管理與兼容性Token 體系作為獨立 npm 包發布后下游代碼庫依賴特定版本。Token 重命名或刪除會構成破壞性變更需要 Semver 主版本號升級。治理手段是引入deprecated標記與別名機制在 Token 倉庫中保留舊名稱一段時間給予下游遷移窗口。代價是 Token 倉庫會累積歷史別名需要定期做廢棄清理否則命名空間會逐漸污染。4.4 適用邊界與禁用場景Token 體系不適用于以下場景。第一營銷活動頁面生命周期短通常 1 至 2 周引入 Token 治理的收益低于成本。第二數據可視化場景如圖表顏色由數據驅動而非設計系統定義Token 化反而限制靈活性。第三原型與 demo 代碼迭代頻繁Token 查詢的摩擦會拖慢驗證速度。第四第三方主題完全由用戶控制的應用應在運行時切換 CSS 變量而非通過 Token 體系構建多套產物。結論Design Token 單一真源的工程化落地核心是建立原始、語義、組件三層分層模型并通過 Style Dictionary 實現 Figma 到多端代碼的自動轉譯。分層模型的價值在于隔離變化——品牌色調整只需改原始 Token組件級樣式自動跟隨語義層調整只需改語義 Token原始層不受影響。落地建議分四步推進。第一步在 Figma 中固化 Variables 命名規范導出 W3C 格式的 Token 文件作為唯一源。第二步建立獨立的 Token 倉庫配置 Style Dictionary 轉譯流水線輸出 CSS 變量與 TS 類型。第三步在前端代碼庫接入硬編碼 lint阻斷未經 Token 的顏色與間距使用。第四步建立 Token 版本管理與廢棄流程確保破壞性變更有 Semver 信號與遷移窗口。Token 體系不是一次性工程而是持續的治理過程。工具鏈是骨架命名規范與 lint 約束才是確保設計稿與代碼長期一致的真正機制。