penpot-build-from-code
Translate an existing application page, view, or component code into a Penpot screen that is bound to the existing design system — mapping code styles onto semantic tokens and reusing library components instead of raw shapes. Use when the user has real code/markup and wants it reconstructed in Penpot, section by section, on-system. Triggers: 'build this in Penpot from code', 'turn this React/HTML/CSS into a Penpot screen', 'recreate this view in Penpot', 'port this page to Penpot', 'translate this component to Penpot bound to our tokens', 'code to Penpot'.
How do I install this agent skill?
npx skills add https://github.com/penpot/penpot-ai-kit --skill penpot-build-from-codeIs this agent skill safe to install?
- Gen Agent Trust Hubwarn
The skill is designed for design-to-code translation but carries risks due to the dynamic assembly and execution of scripts using untrusted user input, as well as a surface for indirect prompt injection.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Build From Code — Translate App Code into an On-System Penpot Screen
1. Title + How it works
This skill takes an existing application artifact — JSX/TSX, HTML, Vue/Svelte templates, CSS/Tailwind, or a styled-components tree — and reconstructs it as an editable, system-bound Penpot screen. 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). The core move is discover first (Phase 0 enumerates the existing tokens and components you must map onto), then transform the code incrementally, section by section, mapping each CSS/style value to a semantic token and each repeated UI part to an existing library component — never hardcoding hex or off-grid spacing, never reinventing a component as a raw box.
2. The One Rule That Matters Most
Never one-shot a screen, and never hardcode a value that a token already expresses. Build the screen one section at a time (one logical execute_code step per call), and before you write any fill/spacing/radius/type, resolve it against the discovered token set. A code-derived #0066FF is not a fill — it is a lookup into the design system. If no token matches, you propose one and stop; you do not invent it. Translating an entire page in a single call, or pasting raw computed CSS onto shapes, is the failure mode this skill exists to prevent.
3. Penpot MCP Tool Reference
Full surface: shared/penpot-mcp-tool-reference.md. The calls this skill leans on:
- Discovery (read-only):
penpotUtils.tokenOverview(),penpot.library.local(components, tokens),penpotUtils.shapeStructure(shape, depth),penpotUtils.findShape/findShapes. - Screen scaffold:
penpot.createBoard(),board.addFlexLayout(),penpotUtils.addFlexLayout(container, dir). - Section build:
penpot.createRectangle/createText/createEllipse/createPath,component.instance(),container.appendChild(child),child.layoutChild. - Token binding:
shape.applyToken(token, properties[]),token.resolvedValue,penpotUtils.findTokenByName. - Validation:
export_shape(separate tool call) and ashapeStructureread.
4. Plugin API Essentials
The gotchas that bite this skill, as one-liners — full text in shared/plugin-api-gotchas.md:
- #1 Immutable style arrays — assign a brand-new
fills/strokesarray; never mutatefills[0]. - #2 Token application is async (~100 ms) — apply in one
execute_codecall, verifyresolvedValuein a later call. - #3
resize()forces textgrowType: 'fixed'— setgrowType = 'auto-width'/'auto-height'after resizing text you want to flow. - #4 Flex/grid overrides child x/y — position children by append order + gap/padding/align/justify; opt out only with
child.layoutChild.absolute = true. - #5
width/heightandparentX/parentYare read-only — useshape.resize(w, h)andpenpotUtils.setParentXY(shape, x, y). - #6 Detach before mutating an instance's internals —
detach()first and report it. - #7 Append or it isn't on the canvas —
container.appendChild(child)(orpenpot.currentPage.root). - #8 Padding tokens — padding tokens bind on ≥ 2.17;
createScreenWrapper.jsfalls back to mirrored numbers + a ledger exception on older instances. - #9
switchVariant(pos, value)—posis the index intovariants.properties(seestorage.run.ds.componentsByRole[role].axes), never the axis name. - #13b Exact font match —
penpot.fonts.all.find(f => f.name === "…");findByNameis a substring search. - #15
layoutChildafter append — setlayoutChild.*only once the child is inside a layout board;openPageis async. - #16 flipX after fill sizing — run the
clearFlipsweep after every section that uses fill sizing (shared/visual-effects.md). - #17 Layout geometry —
await board.waitForLayoutUpdate()before reading geometry (replaces the sleep-100ms idiom). - Verify any unfamiliar signature with
penpot_api_info(type, member)before using it.
5. Token-Aware Brief Contract
Before any mutation, restate the request as this contract. Act as a senior design-engineer who treats the design system as the single source of truth and never hardcodes a value when a token exists.
- Context — what product/surface is this code from, who uses it, what page/view does it represent (e.g. "settings page of the B2B admin console, desktop").
- Objective — single and specific: "rebuild the
<SettingsPage>component as an editable Penpot board bound to the existingsemantictoken set and reusing theButton,InputField, andCardcomponents." - Inputs — the actual code (component files, CSS/Tailwind config, design tokens export if any), the target Penpot page, the discovered token sets, the discovered component library.
- Constraints — forbidden moves (no raw hex, no off-4px-grid spacing, no reinvented components, no
detach()without approval), inviolable rules (semantic tokens not primitives; existing components over new shapes), viewport width. - Acceptance Criteria — quantitative: ≥95% of color/spacing/radius/type values bound to tokens (validated by
validateScreen.js); zero orphan raw values flagged; every repeated UI part that has a matching library component is an instance, not a shape; all spacing on the 4px grid; layer names semantic (noRectangle 12).
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 (read-only)
Goal: map the existing Penpot design system so code values have somewhere to land. No mutation.
high_level_overview(once).execute_code: runscripts/inspectDesignSystem.js→ enumerate token sets, every token (name,type,resolvedValue), and every library component (name,id, variant axes). Cache intostorage.run.ds.execute_code:penpotUtils.tokenOverview()to confirm the active theme/sets.- Parse the input code into a section outline (header, nav, hero, form, card grid, footer…) and a style inventory (every distinct color/spacing/radius/font value the code uses).
Exit criterion: you have (a) a token map keyed by resolved value, (b) a component catalog keyed by role, (c) a section outline, (d) a style inventory with each value pre-matched to a token or flagged
UNMAPPED. ✋ Checkpoint: present the token/component coverage and the UNMAPPED list. Ask which UNMAPPED values should become proposed new tokens vs. accepted as documented exceptions. Seereferences/01-discovery.md.
Phase 1 — Screen wrapper
Goal: create the screen Board with the right flex container and a RUN_ID ledger entry.
execute_code: runscripts/createScreenWrapper.js(idempotent by board name) → Board sized to the viewport,addFlexLayout('column'), gap + padding bound to spacing tokens (padding mirrored +padding-mirrors-tokenexception on 2.16.x). Exit criterion: one empty, correctly-sized, flex column Board exists; its id is in the ledger. ✋ Checkpoint:export_shapethe empty board; confirm dimensions/orientation before filling.
Phase 2 — Incremental section build (repeat per section)
Goal: build exactly one section from its code fragment, on-system. See references/02-component-assembly.md, references/03-token-binding.md, references/04-incremental-transform.md.
execute_code: runscripts/buildSection.jsfor that one section — prefercomponent.instance()for any part with a matching catalog component; create raw shapes only for genuinely bespoke parts; bind every fill/spacing/radius/type to a token viaapplyToken.execute_code(separate call, after ~100 ms gap from the agent's turn): read backtoken.resolvedValue/shapeStructureto confirm bindings applied.export_shapethe section. Exit criterion: the section renders, every value is token-bound or an approved exception, components are instances. ✋ Checkpoint: show the section; "looks good" approves only this section. Name the next section before continuing.
Phase 3 — Validation & report
Goal: prove the screen is on-system.
execute_code: runscripts/validateScreen.js→ token coverage %, list of orphan raw values, structure sanity (flex set, no auto-named layers, instances vs. shapes).- Produce the structured report (section 12 of AGENTS.md). Exit criterion: acceptance criteria met or every gap explicitly listed for review. ✋ Checkpoint: present coverage + remaining gaps; ask for sign-off.
7. Critical Rules
- Always
high_level_overviewfirst; always Phase 0 discovery before any write. - One section per
execute_codemutation call. Never build multiple sections in one call. - Every color/spacing/radius/border/type value MUST resolve to a token before it is applied. No raw hex/rgb/px on a shape when a matching token exists.
- All spacing/padding/gap MUST be a multiple of 4px (round code values to the nearest grid token; document any exception).
- Prefer semantic-tier tokens over primitives so theming works (
tokens-schema.json). - Any UI part with a matching library component MUST be an instance, not a hand-built shape group.
- Never invent a token. If a code value has no match, propose it (name/value/tier) and stop for approval.
- Token application is async — verify
resolvedValuein a laterexecute_codecall, never the same one. - Never
detach()an instance without explicit approval; report it if you do. - Semantic, kebab-case layer names from the code's role (
nav,header,card-container) — neverRectangle 12. - Write the
RUN_IDledger after each phase; make every create idempotent (existence-check by name).
8. Domain Architecture
The skill produces a screen tree and consumes a design-system map.
- Design-system map (Phase 0 output, cached in
storage.run.ds):tokensByValue: { "#0066FF": "color.action.primary.bg", "16": "spacing.4", ... }— reverse index for fast code-value → token lookup.tokensByName: { "color.action.primary.bg": {type, resolvedValue} }.componentsByRole: { button: {id, variants}, input: {...}, card: {...} }.
- Section outline (from code): ordered list
[{ role, codeFragment, childrenRoles }]. - Style inventory:
[{ codeValue, cssProp, matchedToken | "UNMAPPED" }]. - Screen tree (Penpot output):
Board(flex column) → section Boards(flex) → component instances + bound shapes. - Ledger (
RUN_ID):{ runId, phase, created:[{kind,name,id}], proposedTokens:[...], exceptions:[...], pendingReview:[...] }.
9. Modes & Policies
Default review (Apply-with-review): build each section, then stop at the checkpoint with an export_shape + summary before the next. Link: shared/modes-and-policies.md.
- Safe-set (may auto-apply): semantic layer renaming; replacing a raw value that is exactly equal to a token's resolved value with that token (loss-less swap); adding plugin-data/metadata.
- Always require review: creating the screen Board and any geometry; instantiating/restructuring components; any proposed new token;
detach(); any token match that is a judgement call rather than an exact equality.
10. State Management
Link: shared/state-management.md. Ledger namespace penpot-ai, keyed by RUN_ID (e.g. bfc-2026-06-05-a).
storage.run.ds— cached design-system map (tokens, components) from Phase 0.setSharedPluginData('penpot-ai', '<RUN_ID>.phase', n)— current phase.setSharedPluginData('penpot-ai', '<RUN_ID>.ledger', JSON)—{boardId, sectionsBuilt:[role], proposedTokens, exceptions}.- Resume: re-read the ledger, re-run
inspectDesignSystem.jsto re-derive the DS map,shapeStructure(board)to see which sections exist, continue fromledger.phase. Idempotent steps make re-runs safe.
11. User Checkpoints
| After phase | Artifacts shown | What we ask |
|---|---|---|
| 0 Discovery | Token/component coverage table; UNMAPPED style values | "Which UNMAPPED values become proposed tokens vs. documented exceptions?" |
| 1 Wrapper | export_shape of empty Board + dimensions | "Correct viewport size and column orientation?" |
| 2 Section (each) | export_shape of the section + token-binding summary | "Approve THIS section? (next section is named explicitly)" |
| 3 Validation | Coverage %, orphan-value list, structure sanity | "Sign off, or address listed gaps first?" |
12. Naming Conventions
Link: shared/naming-conventions.md. Domain specifics:
- Screen Board:
screen-<view-name>(kebab), e.g.screen-settings. - Section Boards: the semantic HTML role —
header,nav,main,section,footer, or kebab role names likecard-grid,field-row. - Component instances keep the catalog component name.
- Proposed tokens follow dot-notation tiers from
tokens-schema.json; prefer semantic names that describe intent derived from the code's usage (color.action.primary.bg, notcolor.blue.500).
13. Anti-Rationalization Table
| Excuse the LLM makes | Why it's wrong | Deterministic countermeasure that halts the flow |
|---|---|---|
"The CSS says #0066FF, I'll just set that as the fill." | Hardcoded hex is off-system; it won't theme and fails governance. | Look it up in storage.run.ds.tokensByValue. If matched → applyToken. If not → propose a token and stop for approval. Never set raw hex. |
"Tailwind p-5 is 20px, close enough to set 20." | A literal bypasses the token scale and may be off-grid. | Round to the nearest 4px-grid spacing token and applyToken. Document any exception only with approval. |
"There's a Button component but it's faster to draw a rounded rect with text." | Reinventing a system part breaks updates, variants, and handoff parity. | If storage.run.ds.componentsByRole.button exists, component.instance() it and set props/variant. Raw shapes only for genuinely bespoke parts. |
"I'll build the whole page in one execute_code to save calls." | One-shotting hides failures and produces unbound, unverified output. | Build one section per call; verify token bindings in a later call; export_shape and checkpoint before the next. |
"This code value has no token; I'll add color.misc.x myself to keep moving." | Silently inventing tokens pollutes the system and skips human governance. | Add it to ledger.proposedTokens with name/value/tier and halt at the discovery checkpoint. Do not create it unilaterally. |
| "Token didn't read back; I'll just hardcode the value too as a fallback." | A hardcoded fallback is exactly the orphan value this skill forbids; the read-back is async. | Re-read resolvedValue in a later execute_code call. If it truly failed, fix the binding — never add a raw fallback. |
14. Helper Code Snippets
// Reverse-index lookup: code value -> token (Phase 2, inside buildSection)
const ds = storage.run.ds;
const tokenName = ds.tokensByValue[String(codeValue).toLowerCase()]; // e.g. "#0066ff" -> "color.action.primary.bg"
if (tokenName) {
const token = penpotUtils.findTokenByName(tokenName);
shape.applyToken(token, ["fill"]); // verify resolvedValue in a LATER call
} else {
ledger.proposedTokens.push({ from: codeValue, suggest: "propose a semantic token", tier: "semantic" });
}
return { applied: !!tokenName, codeValue };
// Prefer an existing component over a raw shape
const role = "button";
const cat = storage.run.ds.componentsByRole[role];
let node;
if (cat) {
const comp = penpot.library.local.components.find(c => c.id === cat.id);
node = comp.instance(); // instance, not a redrawn box
} else {
node = penpot.createRectangle(); // bespoke only
}
container.appendChild(node);
return { role, usedComponent: !!cat, id: node.id };
15. Reference Resources
penpot_api_info("LibraryComponent"),penpot_api_info("Shape", "applyToken"),penpot_api_info("Board", "addFlexLayout"),penpot_api_info("Token", "resolvedValue")— verify before use.shared/penpot-mcp-tool-reference.md(tool surface),shared/plugin-api-gotchas.md(traps),shared/tokens-schema.json(token types/tiers),shared/naming-conventions.md,shared/state-management.md.- Related skills:
penpot-build-screen(brief-driven generate flow),penpot-foundations(when the system lacks tokens to map onto — its infer mode),penpot-audit-accessibility(post-build contrast check).
16. Supporting Files
references/
| File | Loaded during | Purpose |
|---|---|---|
references/01-discovery.md | Phase 0 | Enumerate tokens/components; build the reverse value→token index and component-by-role catalog; parse code into a section outline + style inventory. |
references/02-component-assembly.md | Phase 2 | Decide instance-vs-shape; instantiate library components, set variants/props; assemble bespoke parts with flex. |
references/03-token-binding.md | Phase 2 | Map code styles to semantic tokens; the applyToken property names; async verification; proposing new tokens. |
references/04-incremental-transform.md | Phase 2 | The section-by-section loop, ordering, ledger updates, resumability. |
references/05-anti-rationalization.md | All phases | Expanded countermeasures with concrete code-translation traps. |
references/06-error-recovery.md | On failure | Diagnosing failed bindings, orphan values, flex/x-y conflicts, detach mishaps. |
scripts/
| File | Phase | Purpose |
|---|---|---|
scripts/inspectDesignSystem.js | 0 | Enumerate token sets/tokens and library components; build the value→token reverse index + component-by-role catalog; cache in storage. |
scripts/createScreenWrapper.js | 1 | Idempotently create the screen Board with flex column and token-bound padding/gap. |
scripts/buildSection.js | 2 | Build one section from a code fragment: instance components, create bespoke shapes, bind tokens. |
scripts/validateScreen.js | 3 | Compute token coverage, list orphan raw values, check structure sanity (flex, names, instances). |
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.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/penpot/penpot-ai-kit/penpot-build-from-code">View penpot-build-from-code on skillZs</a>