← ALL NOTES
METHOD·2026.09·8 MIN READ
Figma 设计系统的工程化落地
figma-apidesign-systems

这篇记录我在 Scanat 设计系统里,把 Figma 变量和组件真正落到代码侧时采用的一套做法。

我现在会把这件事拆成三层:

  1. Token 层:把 Figma Variables 变成可靠、可重复生成的代码数据;
  2. 组件层:根据团队现状决定交付组件代码,还是交付框架无关的规格;
  3. 校验层:让 Figma、代码和文档站长期保持一致。

这三层连起来以后,设计系统才不只是"一份画得很完整的 Figma 文件",而是一套可以持续更新和核对的工程资产。

Token 层:把 Figma Variables 变成可靠数据源

保留 primitives 和 semantic 两层关系。 Figma Variables 里,我会继续保留两层结构:

Primitives → Semantic → Component

例如:

ocean/700 = #0068B0
text/brand-primary → ocean/700

代码侧会生成两份用途不同的产物。给浏览器直接使用的是 CSS Variables:

CSS
--color-text-brand-primary: #0068B0;

给脚本、校验工具或后续主题处理使用的是结构化 JSON:

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 这类数值可以在生成前先跑断言:

PYTHON
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 类型:

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 组件,团队还需要同时维护两种技术体系,设计系统反而增加了迁移成本。

这类项目里,我更适合交付两样东西:

  1. Token 数据:颜色、字号、间距、圆角、描边等;
  2. 可视化规格:把组件各尺寸、状态和组合完整展示出来。
组件代码组件规格
交付内容可直接 import 的实现Token + 视觉 / 状态参考
技术栈要求需要和产品框架匹配框架无关
使用方式直接使用或替换组件给现有组件套用统一规格

可视化参考站本身不需要使用业务项目的技术栈,因为它承担的是规格展示。

这次我会倾向使用轻量、纯静态的方案,例如 Storybook 的 html-vite 模式。最终产物只是 HTML / CSS / JS,对方打开就能查看,不需要为了看组件规格再搭一套额外框架环境。

怎么决定交付代码还是规格

我现在会先问:这个团队当前已经有什么在运行?

如果新项目还没有组件体系,可以直接建立组件代码库。

如果现有产品已经有稳定组件库,优先让设计系统提供统一 Token 和规格,再由现有组件实现去消费这些规则。

这一步要放在技术栈选择之前。先明确交付形态,再决定 Storybook、React、Vue、Tailwind 或其他工具具体怎么用。

让组件说明也成为 Agent 可读的信息

最近在整理组件库时,我开始把 Figma 组件的 Description 当成设计系统的一部分来维护。

以前 Description 更多是给设计师自己看的备注。现在 Figma Agent、代码 Agent 都会读取组件信息,Description 写得清不清楚,会直接影响 Agent 对组件用途和结构的理解。

Figma Agent 可以先根据组件本身帮我生成一版 Description,再人工检查。写这类说明时,我会特别补几类信息:

  • 组件的用途。 优先写通用使用方式;如果组件本来就只服务于一个明确场景,也直接说明具体场景。
  • 子组件引用关系。 如果当前组件引用了其他组件,要写清楚引用了什么,以及这些子组件在当前组件里承担什么角色。
  • Boolean Property 的含义。 每个布尔属性具体控制哪部分内容、打开和关闭分别会发生什么,都尽量写明白。

例如一个组件里有 Show Leading Icon、Show Description 这类属性,只保留属性名对人来说可能已经够用,Agent 在生成代码或 Storybook 时还需要判断它控制的是哪一层、是否影响布局,以及它和其他子组件是什么关系。Description 可以把这些设计意图直接留在组件旁边。

这件事对 AI 读取组件尤其有用。Agent 能从 Figma 里拿到图层、属性和视觉结构,Description 再补上用途和关系信息,后续生成组件说明、Storybook 或代码时就少一些猜测。

我现在会把组件 Description 看成一种轻量的结构化文档:它跟组件一起维护,离真实设计最近,也能同时服务设计师和 Agent。

把 Figma → Storybook 的重复流程固化成 Skill

当 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 等真实交互

对我来说,这套流程最大的价值是减少设计系统里的"人工翻译"。颜色、间距、组件状态和设计约束尽量保留明确的数据关系,后续修改就有地方可以追踪,也更容易发现哪一层发生了漂移。

CONTINUE READING
从 Figma 组件属性到代码 Props2026.09动态替换 3D 模型时的事件绑定失效2026.09