penpot-component-factory

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

penpot-component-factory — complete, tokenized variants

penpot-component-factory —— 完整的令牌化变体

1. Title + How it works

1. 标题与工作原理

penpot-component-factory
builds components and their full variant matrices; every mutation goes through
execute_code
; validate visually with
export_shape
; read structure with
penpotUtils.shapeStructure
(full tool surface:
shared/penpot-mcp-tool-reference.md
). It builds a base component as a Board with flex layout (every value tokenized via
penpot-foundations
tokens), generates variants across axes, combines them into a variant container (
penpotUtils.createVariantContainer
on ≥ 2.17;
penpot.createVariantFromComponents
as the low-level fallback), and verifies completeness.
penpot-component-factory
用于构建组件及其完整变体矩阵;所有修改操作都通过
execute_code
执行;使用
export_shape
进行视觉验证;通过
penpotUtils.shapeStructure
读取结构(完整工具接口参考:
shared/penpot-mcp-tool-reference.md
)。 它会将基础组件构建为带有弹性布局的 Board(所有值都通过
penpot-foundations
令牌实现令牌化),沿不同维度生成变体,将它们组合到变体容器中(2.17及以上版本使用
penpotUtils.createVariantContainer
;低版本降级使用
penpot.createVariantFromComponents
),并验证完整性。

2. The One Rule That Matters Most

2. 最重要的核心规则

Every interactive component ships every required state, and every value is a token. No hardcoded fills/spacing; no missing
Hover
/
Pressed
/
Focus
/
Disabled
unless the system explicitly says otherwise. Build one variant at a time; checkpoint before combining.
每个交互组件都必须包含所有必填状态,且所有值都必须是令牌。 不允许硬编码填充/间距;除非系统明确说明,否则不得缺失
Hover
/
Pressed
/
Focus
/
Disabled
状态。每次构建一个变体,组合前需设置检查点。

3. Penpot MCP Tool Reference

3. Penpot MCP 工具参考

Full surface:
shared/penpot-mcp-tool-reference.md
. Key calls:
CallWhy
penpot.createBoard()
+
addFlexLayout()
base component container
penpot.library.local.createComponent(shapes)
turn the base into a component
shape.clone()
derive variants from the base
penpotUtils.createVariantContainer([{ shape, properties }])
preferred (≥ 2.17): container + axes + values in one call — behind the duplicate-file gate
penpot.createVariantFromComponents(mainInstances)
low-level fallback when the helper is absent
instance.switchVariant(pos, value)
demonstrate/switch variants (
pos
= index in
variants.properties
)
export_shape
visual checkpoint of the matrix
完整接口参考:
shared/penpot-mcp-tool-reference.md
。核心调用:
调用用途
penpot.createBoard()
+
addFlexLayout()
基础组件容器
penpot.library.local.createComponent(shapes)
将基础元素转换为组件
shape.clone()
从基础组件衍生变体
penpotUtils.createVariantContainer([{ shape, properties }])
推荐方式(≥2.17版本):单次调用完成容器+维度+值的创建——需在文件复制确认后使用
penpot.createVariantFromComponents(mainInstances)
无辅助工具时的低层级降级方案
instance.switchVariant(pos, value)
演示/切换变体(
pos
为
variants.properties
中的索引)
export_shape
变体矩阵的视觉检查点

4. Plugin API Essentials

4. 插件 API 核心要点

Gotcha numbers refer to
shared/plugin-api-gotchas.md
.
  • Build the base as a Board (
    createBoard
    , NOT
    createFrame
    ) with
    addFlexLayout()
    ; set
    dir
    , gaps, padding,
    horizontalSizing
    /
    verticalSizing
    .
  • #4 flex/grid overrides child x/y — order children by append; use
    layoutChild
    for per-child sizing/margins.
  • clone()
    duplicates a shape with all properties — the basis for the variant matrix.
  • #9 variant API — prefer
    penpotUtils.createVariantContainer([{ shape: mainInstance, properties: { Size: "Medium", State: "Default" } }])
    (≥ 2.17) behind the duplicate-file gate; the low-level
    penpot.createVariantFromComponents(mainInstances)
    +
    renameProperty
    /
    addProperty
    /
    setVariantProperty
    path only as fallback. Switch with
    instance.switchVariant(pos, value)
    —
    pos
    is the index in
    variants.properties
    , not the axis name.
    Board.combineAsVariants(ids)
    is listed by
    penpot_api_info
    on 2.17 but unverified — do not use.
  • #6 detach before mutating an instance's internals — and NEVER on a variant instance (see #12).
  • #12 variant MUTATION corrupts the file — this skill's critical failure mode.
    setVariantProperty(pos, value)
    updates the variant properties but not the variant root's internal
    :variant-name
    , so the file fails backend referential-integrity validation: from then on every component-touching mutation is rejected with an HTTP 400 the plugin never surfaces — calls hang ~30 s, the session dies, unflushed mutations roll back. The same poison applies to
    addVariant()
    + rename,
    comp.instance()
    +
    detach()
    on a variant,
    createComponent
    on a detached variant instance, and
    shape.remove()
    on a variant board; recovery is manual. Reading variants and instancing a specific variant are safe. Safe strategy: build each state as a standalone Board and
    createComponent([board])
    one at a time, named
    Component / State
    ; Phase 3 (§6) carries the duplicate-file / verify-saves / fallback procedure.
  • Verify unfamiliar signatures with
    penpot_api_info('VariantContainer')
    /
    penpot_api_info('Variants')
    first.
注意事项编号对应
shared/plugin-api-gotchas.md
中的条目。
  • 将基础组件构建为带有
    addFlexLayout()
    的 Board(使用
    createBoard
    ,而非
    createFrame
    );设置
    dir
    、间距、内边距、
    horizontalSizing
    /
    verticalSizing
    。
  • #4 flex/grid 布局会覆盖子元素的 x/y 坐标 —— 通过追加顺序控制子元素排列;使用
    layoutChild
    为单个子元素设置尺寸/外边距。
  • clone()
    会复制形状的所有属性——是构建变体矩阵的基础。
  • #9 变体 API —— 优先使用
    penpotUtils.createVariantContainer([{ shape: mainInstance, properties: { Size: "Medium", State: "Default" } }])
    (≥2.17版本),且需在文件复制确认后使用;仅作为降级方案时,使用低层级的
    penpot.createVariantFromComponents(mainInstances)
    +
    renameProperty
    /
    addProperty
    /
    setVariantProperty
    路径。使用
    instance.switchVariant(pos, value)
    切换变体——
    pos
    是
    variants.properties
    中的索引,而非维度名称。2.17版本的
    penpot_api_info
    中列出了
    Board.combineAsVariants(ids)
    ,但尚未验证——请勿使用。
  • 修改实例内部属性前需执行 #6 detach(分离) 操作——但绝对不要对变体实例执行分离操作(见#12)。
  • #12 变体修改会损坏文件——这是本技能的严重故障模式。
    setVariantProperty(pos, value)
    会更新变体属性,但不会更新变体根节点内部的
    :variant-name
    ,导致文件无法通过后端的引用完整性校验:自此之后所有涉及组件的修改都会被后端以HTTP 400拒绝,且插件不会提示该错误——调用会挂起约30秒,会话失效,未刷新的修改会回滚。同样的问题也会出现在
    addVariant()
    + 重命名、对变体执行
    comp.instance()
    +
    detach()
    、对已分离的变体实例执行
    createComponent
    、对变体Board执行
    shape.remove()
    等操作中;恢复只能手动进行。读取变体和实例化特定变体是安全的。安全策略: 将每个状态构建为独立的Board,每次一个地执行
    createComponent([board])
    ,命名格式为
    Component / State
    ;第3阶段(§6)包含文件复制/保存验证/降级方案的流程。
  • 遇到不熟悉的签名时,先通过
    penpot_api_info('VariantContainer')
    /
    penpot_api_info('Variants')
    验证。

5. Token-Aware Brief Contract

5. 令牌感知需求契约

  • Context — which design system / token sets are active; component purpose.
  • Objective — single component + its variant axes.
  • Inputs — base layout, the token set (
    penpot-foundations
    ), required axes.
  • Constraints — all values tokenized; all required states present; naming
    Property=Value
    .
  • Acceptance Criteria — matrix complete; zero hardcoded values; every state legible & AA-contrast; names follow convention.
Act as a senior component engineer.
  • 上下文 —— 当前使用的设计系统/令牌集;组件用途。
  • 目标 —— 单个组件及其变体维度。
  • 输入 —— 基础布局、令牌集(
    penpot-foundations
    )、必填维度。
  • 约束 —— 所有值令牌化;所有必填状态齐全;命名格式为
    Property=Value
    。
  • 验收标准 —— 变体矩阵完整;零硬编码值;所有状态清晰可读且对比度达AA级;命名符合规范。
请以高级组件工程师的身份执行操作。

6. Mandatory Workflow

6. 强制工作流

Visual self-review (mandatory): before every ✋ checkpoint that shows visual work, run the export → look → fix loop from
shared/visual-self-review.md
— export the unit you just built, inspect the image yourself against the checklist, fix visible defects (max 2 iterations), and present that same export with any remaining defects named.
Phase 0 — Discovery.
high_level_overview
; read tokens (
tokenOverview()
) and existing components. Decide axes (
references/01-variant-axes.md
). ✋ Checkpoint: confirm the axis matrix.
Phase 1 — Base. Build the base Board with flex layout, tokenized (
scripts/buildComponentBase.js
), then
createComponent
. ✋ Checkpoint: review base (
export_shape
).
Phase 2 — Variants as components. For each matrix cell, clone the base, retokenize per state, and
createComponent
it
—
createVariantFromComponents
needs a component per variant. Use
scripts/createVariants.js
(collects each cell's main-instance id). ✋ Checkpoint: review the cells.
Phase 3 — Group, name axes, organize. ⚠️ HIGH-RISK PHASE — read
shared/plugin-api-gotchas.md
#12 first.
Mutating variant components (
setVariantProperty
, axis renames, detaching/removing variants) has corrupted files and hung all subsequent saves in live sessions (the backend rejects every save with an unsurfaced HTTP 400). Before this phase: ✋ Checkpoint — ask the user to duplicate the file (or confirm they accept the risk on a scratch file). Then apply one variant mutation, verify the file still saves (a later read-only call must succeed and persist), and only then continue. If any call hangs ~30 s, stop immediately and switch to the fallback below.
The variant flow:
scripts/createVariantGroup.js
is gated — it returns
{ halted: true }
unless
storage.cf.fileDuplicatedConfirmed === true
(set it in a prior call only after the user duplicated the file and confirmed saves work). It then prefers
penpotUtils.createVariantContainer(records.map(r => ({ shape: main, properties: r.properties })))
(path
"helper"
, ≥ 2.17: axes + values in one call), falling back to
penpot.createVariantFromComponents
  • renameProperty
    /
    addProperty
    /
    setVariantProperty
    (path
    "legacy"
    , kept verbatim in the script). Either way it clears the container fill (#11), gives the container a flex layout (variants stack at the same spot otherwise:
    container.flex || container.addFlexLayout()
    ; row, gaps, padding,
    wrap
    ) and returns
    { containerId, path, properties, components, next }
    . Save verification: after the call, make a trivial change in the Penpot UI and confirm it persists before any further mutation — a ~30 s hang means the file is poisoned; discard the duplicate and switch to the fallback below. ✋ Checkpoint.
Fallback (always safe): skip the variant container entirely. Keep the per-cell components from Phase 2 as standalone components named
Component / Axis=Value, …
(e.g.
Button / Size=Medium, State=Hover
), created one at a time with a flush pause between calls, arranged in a labeled grid. Penpot groups them by the
/
prefix and the user can convert them to real variants in the UI later.
Phase 4 — Validate.
scripts/validateComponent.js
— every required state present, all values tokenized, naming correct. Optional
scripts/switchVariantDemo.js
to prove switching. Report.
视觉自查(强制要求): 在每个展示视觉成果的✋检查点之前, 需执行
shared/visual-self-review.md
中规定的导出→检查→修复循环——导出你刚构建的单元,对照检查清单自行检查图片,修复可见缺陷(最多2轮迭代),然后提交该导出结果,并说明所有剩余缺陷。
阶段0 — 调研。 执行
high_level_overview
;读取令牌(
tokenOverview()
)和现有组件。确定变体维度(参考
references/01-variant-axes.md
)。✋ 检查点:确认维度矩阵。
阶段1 — 基础组件。 构建带有弹性布局的基础Board,完成令牌化(参考
scripts/buildComponentBase.js
),然后执行
createComponent
。✋ 检查点:评审基础组件(通过
export_shape
导出)。
阶段2 — 变体组件化。 对矩阵中的每个单元格,克隆基础组件,根据状态重新令牌化,然后执行
createComponent
——因为
createVariantFromComponents
需要每个变体对应一个组件。使用
scripts/createVariants.js
(收集每个单元格的主实例ID)。✋ 检查点:评审所有变体单元格。
阶段3 — 分组、命名维度、整理。 ⚠️ 高风险阶段——请先阅读
shared/plugin-api-gotchas.md
的#12条目。
修改变体组件(
setVariantProperty
、维度重命名、分离/删除变体)在实际会话中曾导致文件损坏、后续所有保存操作挂起(后端会以未提示的HTTP 400拒绝所有保存请求)。本阶段开始前:✋ 检查点——要求用户复制文件(或确认他们愿意在临时文件上承担风险)。然后执行一次变体修改,验证文件仍可正常保存(后续的只读调用必须成功且数据持久化),之后再继续操作。如果任何调用挂起约30秒,立即停止并切换到下方的降级方案。
变体流程:
scripts/createVariantGroup.js
是门控的——除非
storage.cf.fileDuplicatedConfirmed === true
(仅当用户复制文件并确认保存正常后,再通过之前的调用设置该值),否则它会返回
{ halted: true }
。之后它会优先使用
penpotUtils.createVariantContainer(records.map(r => ({ shape: main, properties: r.properties })))
(路径为
"helper"
,≥2.17版本:单次调用完成维度+值的创建),降级方案为
penpot.createVariantFromComponents
+
renameProperty
/
addProperty
/
setVariantProperty
(路径为
"legacy"
,脚本中按原样保留)。无论哪种方式,它都会清除容器的填充(#11),为容器添加弹性布局(否则变体会堆叠在同一位置:
container.flex || container.addFlexLayout()
;设置为行方向、间距、内边距、自动换行),并返回
{ containerId, path, properties, components, next }
。保存验证: 调用完成后,在Penpot UI中做一个微小修改,确认修改已持久化后再进行后续修改——如果出现约30秒的挂起,说明文件已损坏;请丢弃该复制文件,切换到下方的降级方案。✋ 检查点。
降级方案(始终安全): 完全跳改变体容器。将阶段2中每个单元格的组件保留为独立组件,命名格式为
组件名 / 维度=值, …
(例如
Button / Size=Medium, State=Hover
),每次创建一个,每次调用之间留出刷新暂停时间,按带标签的网格排列。Penpot会按
/
前缀对它们进行分组,用户之后可以在UI中将其转换为真实变体。
阶段4 — 验证。 运行
scripts/validateComponent.js
——检查所有必填状态是否齐全、所有值是否已令牌化、命名是否正确。可选运行
scripts/switchVariantDemo.js
来演示变体切换功能。最后生成报告。

7. Critical Rules

7. 关键规则

  1. All interactive components include
    Default/Hover/Pressed/Focus/Disabled
    unless the system says otherwise.
  2. Zero hardcoded values — bind to semantic (or component) tokens.
  3. Variant property/value names:
    Property=Value
    , PascalCase (
    Size=Medium
    ,
    State=Hover
    ).
  4. Build as a flex Board; never absolute-position children unless
    layoutChild.absolute
    .
  5. Idempotent: don't duplicate an existing variant.
  6. Detach only when necessary, and report it — never detach a variant instance (gotchas #12).
  7. After grouping, give the variant container a flex layout — variants stack at the same position otherwise.
  8. Variant mutation is a known file-corruption risk (gotchas #12): duplicate the file before Phase 3, verify saves after the first mutation, and fall back to standalone
    Component / State
    components if anything hangs.
  1. 除非系统另有说明,否则所有交互组件都必须包含
    Default/Hover/Pressed/Focus/Disabled
    状态。
  2. 零硬编码值——绑定到语义(或组件)令牌。
  3. 变体属性/值命名格式:
    Property=Value
    ,使用大驼峰命名法(PascalCase),例如
    Size=Medium
    、
    State=Hover
    。
  4. 基于弹性布局的Board构建;除非使用
    layoutChild.absolute
    ,否则绝不对子元素使用绝对定位。
  5. 幂等性:不要重复创建已有的变体。
  6. 仅在必要时执行分离(detach)操作,并进行报告——绝对不要对变体实例执行分离操作(见注意事项#12)。
  7. 分组后,为变体容器添加弹性布局——否则变体会堆叠在同一位置。
  8. 变体修改是已知的文件损坏风险(见注意事项#12):阶段3前复制文件,第一次修改后验证保存是否正常,如果出现任何挂起,降级为独立的
    组件 / 状态
    组件。

8. Domain Architecture

8. 领域架构

Standard axes:
  • Size = Small | Medium | Large
  • Hierarchy = Primary | Secondary | Tertiary
  • State = Default | Hover | Pressed | Focus | Disabled
  • Icon = None | Left | Right
The matrix is the Cartesian product you need — keep it as small as the system allows (don't generate combinations the design language doesn't use). Each variant is a cloned base with state-specific token bindings.
标准维度:
  • Size = Small | Medium | Large
  • Hierarchy = Primary | Secondary | Tertiary
  • State = Default | Hover | Pressed | Focus | Disabled
  • Icon = None | Left | Right
变体矩阵是所需的笛卡尔积——请保持矩阵尽可能小,仅包含系统允许的组合(不要生成设计语言未使用的组合)。每个变体都是克隆的基础组件,并绑定了对应状态的令牌。

9. Modes & Policies

9. 模式与策略

Default review. Variant/component restructuring and
detach()
are never auto-applied (
shared/modes-and-policies.md
).
默认模式为评审。变体/组件重构和
detach()
操作绝不自动执行(参考
shared/modes-and-policies.md
)。

10. State Management

10. 状态管理

Ledger keys under
RUN_ID
:
phase
,
baseComponentId
,
created:[{variant:'Size=Medium,State=Hover', id}]
. Re-derive with a component read after truncation.
RUN_ID
下的记录键:
phase
、
baseComponentId
、
created:[{variant:'Size=Medium,State=Hover', id}]
。如果数据被截断,可通过读取组件重新推导。

11. User Checkpoints

11. 用户检查点

After phaseArtifacts shownWhat we ask
0proposed axis matrixApprove axes?
1base
export_shape
Approve base?
2variant grid
export_shape
Approve variants?
3variant containerApprove combine?
阶段后展示产物需确认内容
0拟定的维度矩阵是否批准维度?
1基础组件的
export_shape
导出
是否批准基础组件?
2变体网格的
export_shape
导出
是否批准变体?
3变体容器是否批准组合?

12. Naming Conventions

12. 命名规范

shared/naming-conventions.md
: component PascalCase (
Button
); variant
Property=Value
; internal layers semantic (
label
,
icon
,
button
).
参考
shared/naming-conventions.md
:组件使用大驼峰命名(PascalCase,例如
Button
);变体格式为
Property=Value
;内部图层使用语义化命名(例如
label
、
icon
、
button
)。

13. Anti-Rationalization Table

13. 反合理化对照表

ExcuseWhy it's wrongCountermeasure (halt)
"I'll add hover/disabled later."Incomplete components ship broken states.All required states are acceptance gates; build them now.
"A plain rectangle is fine for this state."Hardcoded shapes drift from the system.Clone the base and bind state tokens.
"I'll hardcode the hover color."Breaks theming and governance.Use/propose a semantic token (
color.action.primary.hover.bg
).
"Absolute-position the icon, it's easier."Fights flex; breaks resizing.Use flex order +
layoutChild
; absolute only with
layoutChild.absolute
and a reason.
"I'll assume the variant-creation method/args."Variant API is easy to get wrong.Prefer
penpotUtils.createVariantContainer
; low-level
createVariantFromComponents
only when the helper is absent; verify with
penpot_api_info('Variants')
. Never
combineAsVariants
(unverified).
"Variant mutation worked once — I'll batch the rest in one call."One bad
setVariantProperty
poisons the file silently; every later save hangs (gotchas #12).
One mutation → verify saves persist → continue. On any ~30 s hang, stop and use the standalone-components fallback.
借口错误原因应对措施(停止操作)
"我之后再加hover/disabled状态。"不完整的组件会交付有问题的状态。所有必填状态都是验收门槛;必须现在构建。
"这个状态用普通矩形就行。"硬编码的形状会偏离设计系统。克隆基础组件并绑定状态令牌。
"我硬编码hover颜色就好。"会破坏主题化和治理规则。使用/提议一个语义令牌(例如
color.action.primary.hover.bg
)。
"图标用绝对定位更简单。"会与flex布局冲突;破坏缩放能力。使用flex排序 +
layoutChild
;仅当有理由时才通过
layoutChild.absolute
使用绝对定位。
"我就猜一下变体创建的方法/参数。"变体API很容易用错。优先使用
penpotUtils.createVariantContainer
;仅当无辅助工具时才使用低层级的
createVariantFromComponents
;通过
penpot_api_info('Variants')
验证。绝对不要使用
combineAsVariants
(未验证)。
"变体修改成功过一次——我把剩下的批量一次调用完。"一个错误的
setVariantProperty
就会悄无声息地损坏文件;后续所有保存都会挂起(见注意事项#12)。
一次修改 → 验证保存成功 → 再继续。出现任何约30秒的挂起,立即停止并使用独立组件降级方案。

14. Helper Code Snippets

14. 辅助代码片段

js
// Base as a flex Board (Phase 1) — gaps/fill/padding bound to tokens (padding binds on ≥ 2.17; mirror + exception on 2.16.x, gotchas #8)
const board = penpot.createBoard();
board.name = "Button";
const flex = board.addFlexLayout();
flex.dir = "row";
flex.horizontalSizing = "auto"; flex.verticalSizing = "auto";

// gap binds to a token; padding binds on Penpot ≥ 2.17 — try applyToken per side, fall back to the RESOLVED value + a padding-mirrors-token exception
const gapTok = penpotUtils.findTokenByName("spacing.inset.sm");   // or propose it at the checkpoint
if (gapTok) board.applyToken(gapTok, ["columnGap"]);
const padY = penpotUtils.findTokenByName("spacing.8");
const padX = penpotUtils.findTokenByName("spacing.16");
penpot.currentPage.root.appendChild(board);                                     // append before binding layout props
const exceptions = [];
for (const [tok, sides, fallback] of [[padY, ["paddingTop", "paddingBottom"], 8], [padX, ["paddingLeft", "paddingRight"], 16]]) {
  for (const side of sides) {
    try { if (tok) board.applyToken(tok, [side]); else throw new Error("token missing"); }
    catch { flex[side.replace("padding", "").toLowerCase() + "Padding"] = tok ? Number(tok.resolvedValue) : fallback; exceptions.push({ kind: "padding-mirrors-token", token: tok && tok.name, side }); }
  }
}

// FILL POLICY: a button is a SURFACE — bind its bg token (never a literal, never the default white)
board.fills = [];
const bgTok = penpotUtils.findTokenByName("color.action.primary.bg");
if (bgTok) board.applyToken(bgTok, ["fill"]);

const label = penpot.createText("Button");
board.appendChild(label);
const comp = penpot.library.local.createComponent([board]);
return { component: comp.name, id: comp.mainInstance().id, exceptions };
js
// Base as a flex Board (Phase 1) — gaps/fill/padding bound to tokens (padding binds on ≥ 2.17; mirror + exception on 2.16.x, gotchas #8)
const board = penpot.createBoard();
board.name = "Button";
const flex = board.addFlexLayout();
flex.dir = "row";
flex.horizontalSizing = "auto"; flex.verticalSizing = "auto";

// gap binds to a token; padding binds on Penpot ≥ 2.17 — try applyToken per side, fall back to the RESOLVED value + a padding-mirrors-token exception
const gapTok = penpotUtils.findTokenByName("spacing.inset.sm");   // or propose it at the checkpoint
if (gapTok) board.applyToken(gapTok, ["columnGap"]);
const padY = penpotUtils.findTokenByName("spacing.8");
const padX = penpotUtils.findTokenByName("spacing.16");
penpot.currentPage.root.appendChild(board);                                     // append before binding layout props
const exceptions = [];
for (const [tok, sides, fallback] of [[padY, ["paddingTop", "paddingBottom"], 8], [padX, ["paddingLeft", "paddingRight"], 16]]) {
  for (const side of sides) {
    try { if (tok) board.applyToken(tok, [side]); else throw new Error("token missing"); }
    catch { flex[side.replace("padding", "").toLowerCase() + "Padding"] = tok ? Number(tok.resolvedValue) : fallback; exceptions.push({ kind: "padding-mirrors-token", token: tok && tok.name, side }); }
  }
}

// FILL POLICY: a button is a SURFACE — bind its bg token (never a literal, never the default white)
board.fills = [];
const bgTok = penpotUtils.findTokenByName("color.action.primary.bg");
if (bgTok) board.applyToken(bgTok, ["fill"]);

const label = penpot.createText("Button");
board.appendChild(label);
const comp = penpot.library.local.createComponent([board]);
return { component: comp.name, id: comp.mainInstance().id, exceptions };

15. Reference Resources

15. 参考资源

  • penpot_api_info('Board')
    ,
    penpot_api_info('VariantContainer')
    ,
    penpot_api_info('Variants')
    ,
    penpot_api_info('LibraryComponent')
    .
  • penpot_api_info('Board')
    、
    penpot_api_info('VariantContainer')
    、
    penpot_api_info('Variants')
    、
    penpot_api_info('LibraryComponent')
    。

16. Supporting Files

16. 支持文件

references/:
01-variant-axes.md
,
02-flex-grid-layout.md
,
03-variant-api.md
,
04-instance-swap-detach.md
,
05-anti-rationalization.md
,
06-error-recovery.md
. scripts/:
buildComponentBase.js
,
createVariants.js
(one component per cell),
createVariantGroup.js
(gated; helper ≥ 2.17 or legacy path),
switchVariantDemo.js
,
validateComponent.js
.
Doctrine paths.
shared/…
and
policies/…
resolve inside this bundle in native installs (vendored by the installer); in a Claude Code plugin install they live at the plugin root —
${CLAUDE_PLUGIN_ROOT}/shared/…
, two directories up from this file.
references/ 目录:
01-variant-axes.md
、
02-flex-grid-layout.md
、
03-variant-api.md
、
04-instance-swap-detach.md
、
05-anti-rationalization.md
、
06-error-recovery.md
。 scripts/ 目录:
buildComponentBase.js
、
createVariants.js
(每个单元格一个组件)、
createVariantGroup.js
(门控;≥2.17版本使用辅助工具,否则使用旧路径)、
switchVariantDemo.js
、
validateComponent.js
。
规则路径说明。 原生安装时,
shared/…
和
policies/…
路径在此包内解析(由安装程序打包);在Claude Code插件安装中,它们位于插件根目录——
${CLAUDE_PLUGIN_ROOT}/shared/…
,即本文件的上两级目录。