这篇记录把 Figma 组件落到代码组件时,我现在会怎么处理 componentPropertyDefinitions 里的属性。
Figma 里的 Variant、Boolean、INSTANCE_SWAP 都是在设计编辑器里帮助设计师维护和预览组件的。代码组件要面对的是运行时状态、真实交互和 API 设计。两边有大量对应关系,也会出现一些只存在于 Figma 编辑环境里的设置。
我现在不会直接把 Figma 属性一项项翻成 Props,而是先看每个属性在真实产品里代表什么。
Figma 很常见的一种做法,是用 Status Variant 把 Default / Hover / Focus / Selected 全部摆出来。这样设计师可以在画布里一次看全所有状态。
代码里,Hover、Focus、Disabled 这类状态通常直接交给浏览器和 CSS:
:hover
:focus-visible
:disabled例如 List - Select 在 Figma 里有 Default / Hover / Focus / Selected。落到代码以后,我会这样处理:Hover → :hover;Focus → :focus-visible;Selected → selected Prop。
Selected 仍然需要由业务状态明确传入,因为浏览器不会自动知道某一项是不是当前选中项。
这里需要看真实交互语义,不能只按 Variant 名字映射。之前一个 Select 组件里的 Focus,实际表达的是下拉面板已经展开。这个状态应该跟 open 一类运行时状态绑定,而不是直接套 :focus-visible。
所以检查 Variant 时,我会先确认:这个状态是谁产生的?浏览器是否已经能感知?它代表视觉焦点、业务选中,还是组件展开?
名字只能作为线索,最终还是看状态在产品里的实际含义。
Figma 的 INSTANCE_SWAP 用来替换组件里的图标或子组件。
例如 List - Select 的 Leading Icon 可以在 Figma 右侧面板里换成任意图标实例。
代码里不需要保留"实例交换"这个编辑器机制,通常直接提供一个可选的插槽参数:
<ListSelect leadingIcon={<SearchIcon />} />具体使用 React Element、Component Type 还是 slot API,可以根据代码规范决定。这里真正需要保留的是"这个位置允许传入一个图标或子组件"。
Figma 里的 Variant 轴越多,组件集需要维护的组合就越多。设计阶段经常会把几件一起出现的变化收在一个 Variant 里。
Group - Input with Btn 就遇到过这种情况。Figma 只有一个 Type:
Normal
Icon但这个 Variant 同时改变了两件事:内容区是否可编辑;右侧按钮使用文字还是图标。
这两件事在运行时没有必然绑定关系。如果代码继续只做一个 type="normal | icon",组件只能支持设计稿里已经画出的两种组合。
开发侧拆成两个独立维度以后,组合会更完整,例如:
contentMode="editable | readonly"
buttonMode="text | icon"这样可以自然得到四种组合。
我现在看到一个 Variant 时,会观察切换它以后哪些东西一起发生变化。如果同时改变了多种性质不同的行为或内容,就继续判断这些变化在真实产品里是否需要始终绑定。
Figma Variant 更适合表达设计样本;代码 API 需要表达可以独立变化的运行时维度。
Boolean Property 是另一个容易误判的地方。
Figma 的 Boolean 可以挂在整个 Component Set 上,所以不同 Variant 都会看到同一个开关。每个 Variant 内部可以把这个开关接到不同图层上,Figma 不会因为语义变化而报错。
FormField 里有一个 Show Required:Input / Textarea Variant 控制必填星号;Tag Variant 控制一条"添加成员"的操作链接。
属性名字看起来完全一致,实际控制的是两类不同内容。
这种情况如果直接生成:
showRequired={true}Tag Variant 的 API 就会变得很奇怪,因为这个 Prop 已经失去原来的含义。
我现在会逐个 Variant 检查 Boolean 实际绑定的图层和功能。发现语义不一致时,常见处理有几种:拆成两个更明确的 Props;某个 Variant 不暴露这个 Prop;如果 Variant 本身已经代表完全不同的组件职责,直接拆组件。
代码层的命名最好能稳定描述运行时含义,不需要维持 Figma Component Set 表面上的属性统一。
Figma 为了把组合摆开,经常会做完整或半完整的 Variant 矩阵。
代码实现时,我会先看这些值之间的关系。
同一时刻只能成立一个,保留成 enum:
size="sm | md | lg"
orientation="horizontal | vertical"多个状态可以同时成立,拆成独立 Boolean:
selected
highlighted
removable即使 Figma 里只画了其中几个组合,代码也不需要把这些状态重新绑回一个大 Variant。
反过来,如果 Figma 里把三个互斥状态拆成三个 Boolean,代码也没有必要照搬成 isA / isB / isC。这种写法容易产生同时为 true 的非法组合。运行时互斥的状态更适合一个 enum 或明确的 state value。
我通常会先列一下这些状态在运行时能不能同时成立,再决定 API 形态。
组件文档里经常需要把 Hover、Focus 之类的状态静态摆出来。
真实组件仍然可以继续使用 CSS 原生状态。展示页单独增加演示用开关即可,例如:
forceHover
forceFocus这些开关只用于 Storybook 或组件文档:不进入正式的公开 API 文档;不放进真实业务使用的 Playground;不参与产品状态管理。
这样既能保留完整的视觉对比页,也不会为了截图和文档展示给正式组件增加一组长期维护的假状态。
这类展示能力最好和真实运行时 API 分开看。Storybook 需要"摆出来",产品代码需要"真的发生"。
拿到一个新的 Component Set,我通常按下面这个顺序过一遍。
| Figma 属性 | 代码里常见处理 |
|---|---|
| Hover / Focus / Disabled Variant | CSS / DOM 原生状态 |
| 业务 Selected / Open 状态 | Boolean 或受控状态 Prop |
| 互斥 Variant | enum Prop |
| 可叠加状态 | 多个独立 Boolean Props |
| INSTANCE_SWAP | icon / slot / child component |
| Figma 为减少组合数而耦合的 Variant | 按运行时语义拆成多个 Props |
| 跨 Variant 语义不一致的 Boolean | 拆 Prop、限制适用范围,必要时拆组件 |
| 静态 Hover / Focus 预览 | Storybook 专用 force state |
Figma 和代码组件最终都在描述同一个产品组件,但两边服务的环境不同。Figma 需要方便设计师预览、切换和维护样本;代码需要保证状态来源清楚、组合合法、API 好用。
我现在更习惯先把 Figma 属性当成一份设计侧的信息输入,再根据运行时语义整理出代码 Props。这样生成出来的组件 API 会更接近产品真实行为,也能减少为了还原 Figma 编辑器结构留下来的冗余状态。