关于 cc-figma-component
cc-figma-component 是一项根据组件契约生成 Figma 组件集的技能。它推导组件级令牌并绑定 Figma 变量,同时遵循契约定义的变体与结构。
主要功能
- 01读取组件契约
- 02推导组件级令牌
- 03将属性绑定到 Semantic 变量
- 04按变体轴与状态构建组件集
- 05应用契约定义的组件结构
适用场景
- 01根据契约生成 Figma 组件
- 02按属性和状态创建变体集
- 03确保组件视觉属性使用 Semantic 变量
MCP 相关
该技能通过 Figma 的 `use_figma` 调用,在 Figma 文件中检查和创建页面、组件及变量绑定。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nvillapiano/ component-contracts-figma --skill cc-figma-component安装后在 Claude Code 中输入/cc-figma-component确认已加载。
详细介绍
cc-figma-component — 组件契约组件技能
根据组件契约生成 Figma 组件集。此技能会读取契约、推导组件范围的(Tier 3)令牌,并在 Figma 中构建完全绑定变量的组件集。
前置条件 — 请先加载以下技能:
figma-use— 插件 API 语法规则(每次调用use_figma前都必须加载)figma-generate-library— 设计系统工作流和组件创建模式- 必须已运行
cc-figma-tokens— 目标 Figma 文件中必须存在Primitives和Semantic集合
在此技能中调用 use_figma 时,始终传入 skillNames: "cc-figma-component"。
全局规则
帧尺寸默认设为 HUG
此技能创建的每个帧都必须在创建后立即将两个尺寸属性设为 'HUG',无一例外。
node.layoutSizingHorizontal = 'HUG';
node.layoutSizingVertical = 'HUG';
这适用于:基础组件帧、每个变体副本、Generation Notes 帧,以及运行期间创建的任何其他帧。切勿依赖 Figma 的默认设置——它会将新帧设为 FIXED,尺寸为 100px,导致组件无论内边距如何都呈纵向比例。仅当契约中明确将插槽子项的 figma.sizing 设为 "fixed" 时,才可偏离 HUG。在这种情况下,直接使用契约中的 figma.width 和 figma.height 值:
if (child.figma?.sizing === 'fixed') {
node.layoutSizingHorizontal = 'FIXED';
node.layoutSizingVertical = 'FIXED';
node.resize(child.figma.width, child.figma.height);
}
这是唯一合理的 HUG 默认设置例外。
1. 配置
首先,从项目根目录读取 .component-contracts。任何回复中都不得输出 FIGMA_ACCESS_TOKEN。
FIGMA_ACCESS_TOKEN=... # never output this value
FIGMA_FILE_KEY=... # use for all use_figma calls
TOKENS_DIR=... # root directory of token files
CONTRACTS_DIR=... # root directory of contract files
2. 契约结构
组件契约是位于 {CONTRACTS_DIR}/{id}.contract.json 的 JSON 文件。规划之前,请完整读取该文件。
此技能使用的关键部分:
tokens — 定义组件的 Tier 3 令牌绑定,按类别组织:
{
"tokens": {
"color": {
"background.primary": "brand.500",
"text.primary": "neutral.0",
"border.secondary": "brand.500"
},
"spacing": {
"padding.x.sm": "space.sm",
"padding.y.sm": "space.xs"
},
"typography": {
"label.size": "textSize.sm",
"label.weight": "weight.semibold"
},
"border": {
"radius": "radius.md",
"width": "border.width.sm"
},
"motion": {
"transition": "duration.fast"
}
}
}
props — 定义组件的变体轴(例如 variant、size、state)
a11y — 无障碍要求(ARIA 角色、键盘行为)
platforms — 目标平台(web、ios、android)
3. Tier 3 令牌推导
组件范围的(Tier 3)令牌由契约的 tokens 部分推导得出,不会单独存储为文件。请在运行时根据以下规则推导:
命名约定
--ds-{componentId}-{role}-{qualifier}
其中:
componentId— 契约的id字段(例如button、accordion)role— 与 Figma 对齐的角色名称(参见下方的角色转换表)qualifier— 根据契约值中的 Semantic 别名名称推导得出
角色转换表
| 契约类别 + 键前缀 | CSS/Figma 角色 |
|---|---|
color.background.* | fill |
color.text.* | text |
color.border.* | border |
color.focus.* | focus |
spacing.* | spacing |
typography.* | typography |
border.radius | border(作为 border-radius) |
border.width | border(作为 border-width) |
motion.* | transition |
限定符推导
限定符取自契约中的 Semantic 别名值,并移除其量级后缀:
"background.primary": "brand.500" → qualifier = "brand" → --ds-button-fill-brand
"background.primary.hover": "brand.600" → qualifier = "brand-hover"
"text.primary": "neutral.0" → qualifier = "neutral-0"
状态后缀(hover、active、focus、disabled)追加在限定符之后:
--ds-button-fill-brand-hover
--ds-button-fill-brand-active
Semantic 变量查找
每个 Tier 3 令牌都必须绑定到 Figma 中对应的 Semantic 变量。请按名称查找变量:
contract value "brand.500" → Semantic variable named "brand/500"
contract value "space.sm" → Semantic variable named "space/sm"
contract value "radius.md" → Semantic variable named "radius/md"
如果未找到 Semantic 变量,请停止操作,并在继续之前向用户报告缺失的变量。
4. 组件集架构
主变体属性(例如 variant)的每个值都对应一个单独的组件集,而不是合并为一个组件集。次级轴(Size、State)则成为每个组件集中的变体属性。
命名约定
{ComponentName}/{VariantValue} → Button/Primary, Accordion/Multiple
- 组件名称:首字母大写的标题格式(
Button、Accordion) - 变体值:首字母大写的标题格式(
Primary、Secondary) - 分隔符:
/
每个组件集中的变体轴
除主要变体外,所有轴都会成为组件集中的属性:
Button/Primary:
Size=Small, State=Default
Size=Small, State=Hover
Size=Small, State=Focus
Size=Small, State=Active
Size=Small, State=Disabled
Size=Small, State=Loading
Size=Medium, State=Default
... (18 total variants per set)
属性命名
- 属性名称:采用标题式大小写,与契约中的属性名称一致(
Size、State) - 属性值:采用标题式大小写(
Small、Medium、Large、Default、Hover、Focus、Active、Disabled、Loading)
标准状态值
始终从契约的 states 数组读取状态,不要使用硬编码列表。将每个值转换为标题式大小写,作为 Figma 属性值(例如,"default" → "Default")。不同组件的状态集合各不相同:Button 有 6 个状态,包括 Loading;Accordion 有 5 个状态,不包括 Loading。
5. 执行模式
默认(快速): 按顺序运行所有阶段,中途不停止。先呈现 Phase 0 计划并等待批准,然后不间断地执行 Phase 1–7。这是所有运行的默认模式。
调试模式: 每个阶段结束后停止,并等待明确批准后再继续。用于诊断问题或验证中间输出。提示中包含“debug mode”即可启用。仅在调试模式下保留脚本;完成后请手动清理。
脚本位置: 所有工作文件都必须写入
scripts/子目录,切勿写入仓库根目录。根目录下的点文件(.mcp-args-phase3.json、.phase2-code-raw.js等)不会由 rimraf 步骤清理,并会在仓库中不断累积。
注意: 将
scripts/添加到.gitignore,这些是生成的工作文件,不是源文件。
6. 工作流
阶段 0 — 发现(只读)
- 读取
.component-contracts配置 - 从
CONTRACTS_DIR读取契约文件 - 检查 Figma 文件,确认是否已有 Semantic/Primitive 集合,以及是否已有同名组件
- 验证 Semantic 和 Primitive 集合是否存在;如果不存在,停止并告知用户先运行
cc-figma-tokens - 从契约的
tokens部分推导出完整的 Tier 3 token 列表 - 查找每个 Semantic 变量,在继续前确认它们都存在
- 根据契约的
props部分构建完整的变体矩阵 - 向用户呈现完整计划:
- 组件名称和变体数量
- 包含 Semantic 别名的完整 Tier 3 token 列表
- 缺失的 Semantic 变量
- 在调试模式下:等待用户明确批准后再继续
阶段 1 — 页面设置
- 为组件创建专用页面:
{ComponentName}(例如Button、Accordion) - 使用
await figma.setCurrentPageAsync(page)切换到该页面 - 返回页面 ID
阶段 2 — 基础组件
先构建一个基础组件(主要变体的 Default 状态):
- 创建一个启用了自动布局的水平 Frame
- 创建后立即使用以下两行确切代码将尺寸设置为 HUG——两行都必须设置,缺一不可:
Figma 默认将新 Frame 的尺寸设为frame.layoutSizingHorizontal = 'HUG'; frame.layoutSizingVertical = 'HUG';FIXED,高度为 100px。省略垂直方向那一行,无论填充值如何,都会生成纵向按钮。请在添加任何子节点或绑定前完成此操作。 - 将所有结构属性(填充、间距、圆角)绑定到语义变量
- 根据契约的
composition.slots部分添加子节点。使用childOrder确定渲染顺序——不要假设顺序固定:- 读取
composition.slots.{slotName}.childOrder——此数组定义了添加到 Frame 中的子节点顺序 - 对于
childOrder中的每个子节点,读取其figma提示:figma.textProp— 创建文本节点,并将其连接到该组件属性名称figma.booleanProp— 创建图标占位 Frame,设置visible: false,并将其连接到该 BOOLEAN 属性名称figma.position: "trailing"— 无论其他子节点的顺序如何,此子节点都放在最后figma.rotateOnExpand— 在生成说明中注明旋转无法绑定变量figma.visibleWhen— 在生成说明中注明可见性由状态驱动
- 如果未定义
composition.slots,则回退为:隐藏的图标占位 Frame,然后是标签文本节点 - 图标占位 Frame 始终为固定尺寸(md 为 20×20),无填充,默认设置
visible: false
- 读取
- 将所有视觉属性绑定到语义变量——不得使用硬编码值
- **添加完所有子节点后,立即执行创建后的尺寸检查。**这会修正压缩器可能漏掉 HUG 设置的 Frame:
// Walk every frame in the component subtree and enforce HUG function enforceHug(node) { if (node.type === 'FRAME' || node.type === 'COMPONENT') { if (node.layoutSizingVertical === 'FIXED') { node.layoutSizingHorizontal = 'HUG'; node.layoutSizingVertical = 'HUG'; } } if ('children' in node) node.children.forEach(enforceHug); } enforceHug(baseComponent); - 在所有节点上递归应用
setExplicitVariableModeForCollection - 验证:
get_screenshot— 确认按钮宽大于高后再继续 - 在调试模式下:等待用户检查点
阶段 3 — 变体矩阵
为主要变体属性的每个值创建单独的组件集:
- 为每种
Size × State组合复制基础组件 - 对每个副本,在应用任何令牌覆盖前,使用以下两行确切代码将尺寸重置为 HUG:
复制操作可能会保留原组件的尺寸模式,但必须明确地对每个副本重新设置,不能例外。duplicate.layoutSizingHorizontal = 'HUG'; duplicate.layoutSizingVertical = 'HUG'; - 按状态应用特定于变体的令牌覆盖(例如,悬停时更改填充颜色)
- 为每个组件命名:
Size=Small, State=Default等(标题大小写,Property=Value) - 调用
combineAsVariants前,对所有变体 Frame 执行 HUG 强制检查。这是安全措施,用于捕捉 Agent 遗漏的 Frame:
完成此检查后,任何变体的高度仍为 100px,都表示存在绑定问题,而非尺寸问题。variants.forEach(v => { v.layoutSizingHorizontal = 'HUG'; v.layoutSizingVertical = 'HUG'; }); - 调用
combineAsVariants创建组件集 - 将组件集重命名为
{ComponentName}/{VariantValue}(例如Button/Primary) - 调用
combineAsVariants后立即:手动以网格布局排列变体,并调整至实际边界:const COLS = 6; // one column per state const GAP = 40; const PADDING = 40; // padding inside component set on all sides variants.forEach((v, i) => { v.x = PADDING + (i % COLS) * (v.width + GAP); v.y = PADDING + Math.floor(i / COLS) * (v.height + GAP); }); // Resize to actual content bounds — never guess, always calculate const maxRight = Math.max(...variants.map(v => v.x + v.width)); const maxBottom = Math.max(...variants.map(v => v.y + v.height)); componentSet.resize(maxRight + PADDING, maxBottom + PADDING); - 对每个变体值(Primary、Secondary、Ghost、Destructive)重复此操作
- 验证:
get_screenshot— 确认所有尺寸的按钮都宽大于高 - 在调试模式下:等待用户检查点
阶段 4 — 变量绑定检查
对每个变体节点:
- 将填充属性绑定到正确的语义颜色变量
- 将描边属性绑定到正确的语义颜色变量
- 将填充和间距绑定到正确的语义间距变量
- 将圆角半径绑定到正确的语义圆角变量
- 绑定后,对每个节点及其所有子节点递归调用
setExplicitVariableModeForCollection - 验证:
get_screenshot— 确认颜色正确解析(没有淡显或未解析的变量)
阶段 5 — 组件属性
- 定义与契约中
props部分匹配的组件属性:- TEXT
label— 链接到所有变体(包括 Loading)中 Label 文本节点的characters。切勿替换为类似 "Saving" 的硬编码字符串——标签文本始终由label属性值驱动。 - BOOLEAN
iconStart— 通过componentPropertyReferences链接到 Icon 占位符的visible属性。默认值为false(隐藏)。这是切换图标插槽可见性的正确方式。 - BOOLEAN
iconOnly— 在组件集中定义。不自动链接到布局(参见已知限制)。 - BOOLEAN
fullWidth— 在组件集中定义。不自动链接到布局(参见已知限制)。
- TEXT
- 不要将
disabled或loading添加为 BOOLEAN 属性——它们仅作为变体状态处理。 - 除非文件中存在实际的图标组件,否则不要为图标添加
INSTANCE_SWAP。 - 验证:确认属性面板与契约中的属性匹配。
阶段 6 — 画布注释
最终验证前,在组件页面上创建一个 ⚠️ Generation Notes 框架,记录 Agent 的所有假设、标记的问题或未能解决的事项。这样,任何打开文件的人都能了解输出内容。
该框架应放置在所有组件集下方,并与其清晰分隔。使用浅黄色填充(#FFFBEB),自动布局方向设为垂直,并明确设置尺寸:
notesFrame.layoutSizingHorizontal = 'HUG';
notesFrame.layoutSizingVertical = 'HUG';
这样可确保框架扩展以容纳所有注释文本,而不会在固定高度下发生裁切。添加一个标题文本节点,后接各条单独的注释。
如适用,务必为以下每种情况添加注释:
| 条件 | 要写入的注释 |
|---|---|
focus/default 与某个变体填充色相同 | "Focus ring color conflict: focus/default may be invisible on [variant] fill. Define a focus/onBrand Semantic token and re-run." |
| Loading 状态没有 spinner 组件 | "State=Loading: No spinner component found. Label text preserved. Add a spinner instance and wire to iconStart, or create a dedicated loading indicator." |
| 任意令牌绑定到 Primitives 而非 Semantic | "Token gap: [token name] has no Semantic alias. Bound to Primitive directly. Add a Semantic alias to align with the token architecture." |
fullWidth 或 iconOnly 未连接到布局 | "fullWidth / iconOnly are defined as component properties but do not drive layout automatically. See known constraints." |
契约中存在 iconEnd 属性 | "iconEnd is not modeled in Figma — no trailing icon slot created. Add a second icon placeholder frame and wire to an iconEnd BOOLEAN if needed." |
| 契约中的任意属性未在 Figma 中建模 | "[prop name] from contract not modeled in Figma: [reason]." |
| 生成过程中作出了任何假设 | 明确记录该假设。 |
如果没有注释,也要创建该框架,并添加文本 "No issues — all tokens resolved, all props wired."
阶段 7 — 最终验证
get_metadata— 确认结构:变体数量、属性定义、层级结构。get_screenshot— 进行视觉检查:所有尺寸下按钮的宽度均大于高度、文本未被裁切、元素没有重叠、间距正确、变体没有被裁切。- 返回结构化摘要:
{
"component": "Button",
"componentSets": [
{ "name": "Button/Primary", "variantCount": 18, "id": "..." },
{ "name": "Button/Secondary", "variantCount": 18, "id": "..." },
{ "name": "Button/Ghost", "variantCount": 18, "id": "..." },
{ "name": "Button/Destructive", "variantCount": 18, "id": "..." }
],
"propertiesDefined": ["Size", "State", "label", "iconStart", "iconOnly", "fullWidth"],
"annotations": ["list of notes written to canvas"],
"tier3Tokens": [...],
"pageId": "..."
}
-
清理——每次运行结束时都必须执行此操作,无论是否成功。调试模式是唯一例外。
这些脚本是一次性的。 不要建议保留、复用或重新生成它们。不要指导用户运行 terser 或重写 invoke 文件。每次运行都会从头生成一套新文件。将它们视为构建缓存,而非源文件。
删除
scripts/目录以及写入仓库根目录的任何工作文件:npx rimraf scripts/ rm -f .mcp-*.json .mcp-*.txt .*-phase*.js .*-phase*.json .*-args*.json .*-code*.js .*-code*.json .tmp-*.js在调试模式下,保留所有文件以供检查。
7. 变量绑定规则
- 组件只能绑定到语义变量,绝不能直接绑定到原语
- 每个有对应令牌的视觉属性都 MUST 绑定变量,不得使用硬编码值
- 执行任何变量绑定操作后,都 MUST 对每个节点及其所有子节点递归调用
setExplicitVariableModeForCollection - 在文本节点上使用
setBoundVariable前,必须先加载字体:await figma.loadFontAsync({family, style}) cornerRadius不支持直接使用setBoundVariable,请改用单个圆角属性:topLeftRadius、topRightRadius、bottomLeftRadius、bottomRightRadius- 始终使用
setSharedPluginData/getSharedPluginData,绝不要使用setPluginData/getPluginData。非共享版本会使用调试 UUID 作为插件标识符,而该标识符会在每次工具执行时发生变化,导致存储的数据永久无法检索,并使文件越来越臃肿。始终使用component_contracts命名空间:// CORRECT node.setSharedPluginData('component_contracts', 'dsb_key', 'component/button/base'); node.getSharedPluginData('component_contracts', 'dsb_key'); // WRONG — UUID changes per execution, data becomes orphaned node.setPluginData('dsb_key', 'component/button/base');
8. 已知限制
-
Frame 尺寸默认固定为 100px — Figma 会将新建 Frame 的尺寸设为
FIXED,高度为 100px。必须在每个 Frame、每个变体副本和 Generation Notes Frame 上显式设置这两行:node.layoutSizingHorizontal = 'HUG'; node.layoutSizingVertical = 'HUG';只设置水平尺寸而省略垂直尺寸,是按钮纵横比错误最常见的原因。没有简写方式——始终需要设置这两行。
-
字间距 — 无法绑定到变量(em 单位与 Figma FLOAT 不兼容)。请根据 token 的原始值换算出固定像素值并设置。
-
combineAsVariants 不会自动布局——变体会堆叠在 (0,0) 位置。之后应立即手动进行网格布局。始终使用
Math.max,根据所有变体的位置、尺寸和内边距,将组件集调整为实际内容边界。切勿猜测调整后的尺寸。 -
必须在
parent.appendChild(child)之后设置layoutSizingHorizontal/Vertical = 'FILL'——提前设置会抛出错误。 -
Ghost 模式 — 每次绑定操作后,都要在节点及其所有子节点上递归调用
setExplicitVariableModeForCollection。跳过这一步会导致变量显示为未解析。 -
变体数量上限 — 如果每个组件集的 Size × State 大于 30 个变体,请在创建前告知用户。可考虑拆分为多个子组件。
-
焦点环颜色冲突 — 此技能不会对焦点环颜色做出判断。读取 contract 的
color.focus.*token,并将其中定义的 Semantic 变量用于所有变体。如果focus/default与某个变体的填充色相同,请在 Generation Notes Frame 中标明,并建议用户定义focus/onBrandSemantic token。切勿自行替换为neutral/0或其他颜色。 -
fullWidth和iconOnly无法自动控制布局 — Figma Plugin API 的componentPropertyReferences仅支持visible、characters和mainComponent。它不支持将布尔值关联到layoutSizingHorizontal,也不支持在仅图标布局和完整布局之间切换。这些属性是为规格说明和 Code Connect 定义的。请向使用者说明此限制——布局变更需要手动创建变体或后续使用插件处理。 -
Figma 插件沙箱——未定义
atob— 插件运行时没有原生的 base64 解码功能。插件脚本中不要使用atob或btoa。如果脚本超过 20k 字符限制而必须分块,请使用 base64 polyfill,或将逻辑拆分成多个连续的use_figma调用。 -
旋转值始终使用角度,绝不使用弧度 — Figma Plugin API 的
node.rotation属性接受角度。始终直接传入 contract 中的角度值(例如-90)。切勿将角度转换为弧度(-Math.PI/2)。传入弧度会导致 Figma 界面显示类似-1.57°的值,看起来不正确,尽管视觉效果是正确的。contract 的expandedRotation和defaultRotation字段始终以角度表示。 -
Figma 插件沙箱——
eval已被屏蔽 — 需要动态执行代码时,请使用new Function(code)()作为解决方法。这是在 Figma 环境中组装并运行分块插件脚本的唯一可靠模式。 -
图标插槽可见性 — 图标占位 Frame 默认必须隐藏(
visible: false),并通过componentPropertyReferences关联到类型为visible的 BOOLEAN 组件属性(iconStart)。切勿让图标插槽始终可见,否则所有不使用图标的变体都会出现空框。