这篇记录我在 Scanat 设计系统里,把 Figma 变量和组件真正落到代码侧时采用的一套做法。
我现在会把这件事拆成三层:
这三层连起来以后,设计系统才不只是"一份画得很完整的 Figma 文件",而是一套可以持续更新和核对的工程资产。
保留 primitives 和 semantic 两层关系。 Figma Variables 里,我会继续保留两层结构:
Primitives → Semantic → Component例如:
ocean/700 = #0068B0
text/brand-primary → ocean/700代码侧会生成两份用途不同的产物。给浏览器直接使用的是 CSS Variables:
--color-text-brand-primary: #0068B0;给脚本、校验工具或后续主题处理使用的是结构化 JSON:
"text/brand-primary": {
"value": "#0068B0",
"aliasOf": "ocean/700"
}CSS 只需要最终值;JSON 会保留 aliasOf 这类关系信息。
主题切换时,这层引用关系很有用。同一个 semantic token 可以在 Light / Dark Mode 下分别指向不同 primitive,组件仍然只使用 text/brand-primary。
提取逻辑要能重复执行。 Token 提取最好做成脚本,而不是一次性手工抄值。
如果设计稿里的变量名或数值发生变化,重新运行脚本就应该能得到新的代码产物。Figma API 能直接读取的数据走自动提取;用途说明、特殊备注等 API 读不到的内容,可以单独维护。
手工维护的数据需要和 Figma 实测值做交叉检查。例如 Radius、Spacing 这类数值可以在生成前先跑断言:
def cross_check():
for label, table in (("Radius", RADIUS), ("Spacing", SPACING)):
mine = {name: val for name, val, _ in table}
theirs = VARIABLES_CHECK[label]
assert mine == theirs, f"{label} 数值不一致:{mine} vs {theirs}"数值对不上时直接停止生成,比生成以后再靠人肉发现安全得多。
命名规则尽量机械可推导。 Figma 名称到代码名称最好能按固定规则转换。
| Figma | 代码 |
|---|---|
text/brand-primary | --color-text-brand-primary |
Large Radius | --radius-large |
CardShadow | --shadow-card |
规则稳定以后,新 token 不需要再逐个讨论命名,人和 AI 工具都可以按同一套规则处理。
单位换算提前定规则。 如果代码统一使用 rem,Figma 里的 px 可以按固定比例转换,例如 1rem = 16px。同时列出保留 px 的例外。
我会保留 px 的常见情况:1px / 1.5px 这类 Border;9999px 这类 Full Radius 技巧值;其他需要保持绝对像素意义的细节。例外直接写进规则,避免同类 token 被不同人用不同方式处理。
以 Figma 的真实数据为准。 变量提取过程中,我会尽量避免根据命名猜数据。
实际做这套系统时遇到过几类问题:某些变量名字不统一;名称看起来属于一种颜色,实际值却来自另一个色系;口头说"只改了这几个值",全量读取后发现还有其他变化;Figma 变量集合很大,但真正被组件使用的只是其中一部分。
遇到这类情况,直接用 Figma Plugin API 读取源数据再判断。
代码里也不需要为了看起来"完整"补一套没有核对过的 token。当前只确认了一个子集,就明确记录这是已使用子集,后续再逐步补。
如果团队决定统一命名,也可以在代码生成层做规范化。例如源数据里有历史命名不一致,但下游脚本需要稳定格式,可以在生成规则里统一处理,同时保留原始映射关系方便追溯。
Token 处理完以后,组件怎么落地要先看项目现状。
我会先分成两种情况:新项目,从零搭组件库;已有产品,已经有成熟的组件实现。
这一步会直接决定最终交付什么。
新项目里,设计系统可以直接进入代码组件。这时我会重点处理几件事。
用类型系统约束非法组合。 Figma 组件里经常存在一些组合限制,例如某个 styleVariant 只支持特定 theme。这类规则可以直接写进 TypeScript 类型:
type ButtonAppearance =
| { styleVariant?: "fill"; theme?: ButtonTheme }
| { styleVariant: "line" | "tint"; theme?: "light" }这样非法组合在开发阶段就会报错,不需要等到视觉检查时再发现。
Figma 状态先判断是不是运行时状态。 Figma Component Set 经常把 Hover / Focus / Disabled / Filled / Placeholder 都做成 Variant。代码里可以先检查浏览器是否已经能处理:Hover → :hover;Focus → :focus-visible / :focus-within;Disabled → :disabled;Placeholder → ::placeholder。
校验结果、Selected、Open 这类浏览器无法自行判断的状态,再作为真实 Props 或组件状态处理。这样组件 API 会比 Figma Variant 更精简,也更接近真实运行逻辑。
保留状态效果的生成方式。 有些视觉状态在 Figma 里是通过叠加产生的,例如 Hover 使用一层半透明色覆盖在品牌底色上。代码里我会尽量保留同样的生成逻辑,例如继续通过 token + 半透明 overlay 计算,而不是直接把最终结果抄成一个新的十六进制色值。这样以后品牌 token 改变,Hover 效果可以一起更新。
组件样式直接消费 Token。 如果项目使用 Tailwind,可以直接在类名中引用 CSS Variables:
bg-[var(--color-bg-brand)]
text-[var(--color-text-primary)]这样组件样式和 Token 文件之间保持直接关系,减少中间再维护一层重复映射。
已有产品里,重新写一整套新组件通常没有必要。
Scanat 当时的项目已经在 Vue + bootstrap-vue-next 上运行。如果我另外交一套 React 组件,团队还需要同时维护两种技术体系,设计系统反而增加了迁移成本。
这类项目里,我更适合交付两样东西:
| 组件代码 | 组件规格 | |
|---|---|---|
| 交付内容 | 可直接 import 的实现 | Token + 视觉 / 状态参考 |
| 技术栈要求 | 需要和产品框架匹配 | 框架无关 |
| 使用方式 | 直接使用或替换组件 | 给现有组件套用统一规格 |
可视化参考站本身不需要使用业务项目的技术栈,因为它承担的是规格展示。
这次我会倾向使用轻量、纯静态的方案,例如 Storybook 的 html-vite 模式。最终产物只是 HTML / CSS / JS,对方打开就能查看,不需要为了看组件规格再搭一套额外框架环境。
我现在会先问:这个团队当前已经有什么在运行?
如果新项目还没有组件体系,可以直接建立组件代码库。
如果现有产品已经有稳定组件库,优先让设计系统提供统一 Token 和规格,再由现有组件实现去消费这些规则。
这一步要放在技术栈选择之前。先明确交付形态,再决定 Storybook、React、Vue、Tailwind 或其他工具具体怎么用。
最近在整理组件库时,我开始把 Figma 组件的 Description 当成设计系统的一部分来维护。
以前 Description 更多是给设计师自己看的备注。现在 Figma Agent、代码 Agent 都会读取组件信息,Description 写得清不清楚,会直接影响 Agent 对组件用途和结构的理解。
Figma Agent 可以先根据组件本身帮我生成一版 Description,再人工检查。写这类说明时,我会特别补几类信息:
例如一个组件里有 Show Leading Icon、Show Description 这类属性,只保留属性名对人来说可能已经够用,Agent 在生成代码或 Storybook 时还需要判断它控制的是哪一层、是否影响布局,以及它和其他子组件是什么关系。Description 可以把这些设计意图直接留在组件旁边。
这件事对 AI 读取组件尤其有用。Agent 能从 Figma 里拿到图层、属性和视觉结构,Description 再补上用途和关系信息,后续生成组件说明、Storybook 或代码时就少一些猜测。
我现在会把组件 Description 看成一种轻量的结构化文档:它跟组件一起维护,离真实设计最近,也能同时服务设计师和 Agent。
当 Agent 多次执行同一类任务以后,我会把稳定下来的流程整理成一个 Skill。
我目前已经把"从 Figma 读取组件并实现 / 制作 Storybook"做成了一个 Skill。这样下次处理新组件时,不需要重新写一大段 Prompt 去解释读取顺序、组件分析方式和交付要求,Agent 可以按同一套流程执行。
这套流程大致是:
读取 Figma 组件
↓
读取 Description、属性和子组件关系
↓
理解 Variant / Boolean 等组件状态
↓
整理实现需要的组件结构和规格
↓
生成 / 更新 Storybook
↓
检查视觉、状态和交互Skill 里适合放那些长期稳定、会反复出现的规则。单个组件的特殊要求继续留在组件 Description 或当前任务里,这样通用流程和具体组件信息不会混在一起。
对我来说,Skill 的价值主要是减少重复说明,同时让不同批次的组件按照同一套步骤被读取和实现。设计系统后面继续扩展时,只需要维护这套流程本身,再让 Agent 持续复用。
最终需要长期保持一致的是:
Figma Variables
↓
Token Files
↓
Components / Documentation设计系统一旦开始持续更新,单次同步很快就会失效,所以我会给这条链路留出固定的校验方式。
文档站直接使用真实 Token。 组件规格站不要复制一套颜色和间距值。文档本身直接引用生成出来的 tokens.css。这样 Token 数据出现问题时,文档页面会马上反映出来,文档同时承担了一层可视化校验。
定期做全量比对。 Figma Variables、代码 Token 和文档站最好定期做完整比较。只抽几个典型值检查,很容易漏掉新增变量、某个不常用集合或者某个最近改过的尺寸。如果 Figma 更新频繁,我会在每次同步时重新获取完整源数据,再比较前后差异。这样也能避免"口头只说改了 A,但实际 B、C 也一起变了"的情况。
Hover / Focus 要实际操作一次。 自动化脚本很适合检查 token 和静态样式,但伪类状态仍然需要真实输入。组件完成后,我会至少手动检查:鼠标 Hover;Tab 键 Focus;Disabled;Selected / Open 等业务状态;不同 Appearance 下的同类状态。如果多个 Appearance 共用一套状态规律,可以先建立规则,再抽样确认推导结果确实一致。
我现在会按这个顺序处理 Figma 设计系统的工程化:
Figma Variables
↓
读取真实数据
↓
按规则生成 Token
↓
校验命名 / 数值 / 单位
↓
判断团队现有组件体系
↓
新项目:生成代码组件
已有项目:交付 Token + 组件规格
↓
文档站直接消费真实 Token
↓
Figma / Token / 文档全量核对
↓
手动检查 Hover / Focus 等真实交互对我来说,这套流程最大的价值是减少设计系统里的"人工翻译"。颜色、间距、组件状态和设计约束尽量保留明确的数据关系,后续修改就有地方可以追踪,也更容易发现哪一层发生了漂移。