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'.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
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'.
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
(penpot.createVariantFromComponents(boards)), and verifies completeness.
2. The One Rule That Matters Most
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.
3. Penpot MCP Tool Reference
Full surface: shared/penpot-mcp-tool-reference.md. Key calls:
Call
Why
penpot.createBoard() + addFlexLayout()
base component container
penpot.library.local.createComponent(shapes)
turn the base into a component
shape.clone()
derive variants from the base
penpot.createVariantFromComponents(mainInstances)
group component main-instances into a variant container
instance.switchVariant(pos, value)
demonstrate/switch variants
export_shape
visual checkpoint of the matrix
4. Plugin API Essentials
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 — penpot.createVariantFromComponents(mainInstances) (no combineAsVariants); switch with instance.switchVariant(pos, value).
#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.
5. Token-Aware Brief Contract
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.
6. Mandatory Workflow
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 calls
penpot.createVariantFromComponents(mainInstances), then renames the auto-created property to your
first axis, adds the remaining axes (variants.addProperty() + renameProperty), and sets each
component's value via setVariantProperty(pos, value). Finally it gives the variant container a flex
layout — createVariantFromComponents stacks the variants at the same spot, so always apply a flex
(container.flex || container.addFlexLayout(); row, gaps, padding, wrap) so they arrange and the
container reflows to fit. ✋ 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.
7. Critical Rules
All interactive components include Default/Hover/Pressed/Focus/Disabled unless the system says otherwise.
Zero hardcoded values — bind to semantic (or component) tokens.
Build as a flex Board; never absolute-position children unless layoutChild.absolute.
Idempotent: don't duplicate an existing variant.
Detach only when necessary, and report it — never detach a variant instance (gotchas #12).
After grouping, give the variant container a flex layout — variants stack at the same position otherwise.
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.
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.
9. Modes & Policies
Default review. Variant/component restructuring and detach() are never auto-applied
(shared/modes-and-policies.md).
10. State Management
Ledger keys under RUN_ID: phase, baseComponentId, created:[{variant:'Size=Medium,State=Hover', id}]. Re-derive with a component read after truncation.