Loading...
Loading...
Build and maintain Penpot components with COMPLETE variant matrices — sizes, hierarchies, and all interactive states (default/hover/pressed/focus/disabled) — fully tokenized and correctly named. Use to create a new component with variants, fill in missing states, or normalize an existing component. Triggers: 'create a Button component with variants', 'build component variants', 'add hover/pressed/disabled states', 'make a variant matrix', 'turn this into a component with sizes', 'normalize this component'.
npx skill4agent add penpot/penpot-ai-kit penpot-component-factorypenpot-component-factoryexecute_codeexport_shapepenpotUtils.shapeStructureshared/penpot-mcp-tool-reference.mdpenpot-foundationspenpotUtils.createVariantContainerpenpot.createVariantFromComponentsHoverPressedFocusDisabledshared/penpot-mcp-tool-reference.md| Call | Why |
|---|---|
| base component container |
| turn the base into a component |
| derive variants from the base |
| preferred (≥ 2.17): container + axes + values in one call — behind the duplicate-file gate |
| low-level fallback when the helper is absent |
| demonstrate/switch variants ( |
| visual checkpoint of the matrix |
shared/plugin-api-gotchas.mdcreateBoardcreateFrameaddFlexLayout()dirhorizontalSizingverticalSizinglayoutChildclone()penpotUtils.createVariantContainer([{ shape: mainInstance, properties: { Size: "Medium", State: "Default" } }])penpot.createVariantFromComponents(mainInstances)renamePropertyaddPropertysetVariantPropertyinstance.switchVariant(pos, value)posvariants.propertiesBoard.combineAsVariants(ids)penpot_api_infosetVariantProperty(pos, value):variant-nameaddVariant()comp.instance()detach()createComponentshape.remove()createComponent([board])Component / Statepenpot_api_info('VariantContainer')penpot_api_info('Variants')penpot-foundationsProperty=ValueVisual self-review (mandatory): before every ✋ checkpoint that shows visual work, run the export → look → fix loop from— 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.shared/visual-self-review.md
high_level_overviewtokenOverview()references/01-variant-axes.mdscripts/buildComponentBase.jscreateComponentexport_shapecreateComponentcreateVariantFromComponentsscripts/createVariants.jsshared/plugin-api-gotchas.mdsetVariantPropertyscripts/createVariantGroup.js{ halted: true }storage.cf.fileDuplicatedConfirmed === truepenpotUtils.createVariantContainer(records.map(r => ({ shape: main, properties: r.properties })))"helper"penpot.createVariantFromComponentsrenamePropertyaddPropertysetVariantProperty"legacy"container.flex || container.addFlexLayout()wrap{ containerId, path, properties, components, next }Component / Axis=Value, …Button / Size=Medium, State=Hover/scripts/validateComponent.jsscripts/switchVariantDemo.jsDefault/Hover/Pressed/Focus/DisabledProperty=ValueSize=MediumState=HoverlayoutChild.absoluteComponent / StateSize = Small | Medium | LargeHierarchy = Primary | Secondary | TertiaryState = Default | Hover | Pressed | Focus | DisabledIcon = None | Left | Rightdetach()shared/modes-and-policies.mdRUN_IDphasebaseComponentIdcreated:[{variant:'Size=Medium,State=Hover', id}]| After phase | Artifacts shown | What we ask |
|---|---|---|
| 0 | proposed axis matrix | Approve axes? |
| 1 | base | Approve base? |
| 2 | variant grid | Approve variants? |
| 3 | variant container | Approve combine? |
shared/naming-conventions.mdButtonProperty=Valuelabeliconbutton| Excuse | Why it's wrong | Countermeasure (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 ( |
| "Absolute-position the icon, it's easier." | Fights flex; breaks resizing. | Use flex order + |
| "I'll assume the variant-creation method/args." | Variant API is easy to get wrong. | Prefer |
| "Variant mutation worked once — I'll batch the rest in one call." | One bad | One mutation → verify saves persist → continue. On any ~30 s hang, stop and use the standalone-components fallback. |
// 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 };penpot_api_info('Board')penpot_api_info('VariantContainer')penpot_api_info('Variants')penpot_api_info('LibraryComponent')01-variant-axes.md02-flex-grid-layout.md03-variant-api.md04-instance-swap-detach.md05-anti-rationalization.md06-error-recovery.mdbuildComponentBase.jscreateVariants.jscreateVariantGroup.jsswitchVariantDemo.jsvalidateComponent.jsshared/…policies/…${CLAUDE_PLUGIN_ROOT}/shared/…