Review a Penpot design against its implemented code (a source component or Storybook story) and emit a structured DRIFT report. Use when an engineer needs to confirm the build matches the design: extract HTML/CSS from a selection, map its tokens/structure/states to the code component, diff them, and list what each side is missing with severity and a reconciliation. Degrades gracefully to a design-system-token check when no code source is supplied. Triggers: 'design to code review', 'does my code match the design', 'design code parity', 'compare Penpot to my component', 'check this against Storybook', 'extract HTML/CSS from this selection', 'find design drift', 'design implementation diff'.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Review a Penpot design against its implemented code (a source component or Storybook story) and emit a structured DRIFT report. Use when an engineer needs to confirm the build matches the design: extract HTML/CSS from a selection, map its tokens/structure/states to the code component, diff them, and list what each side is missing with severity and a reconciliation. Degrades gracefully to a design-system-token check when no code source is supplied. Triggers: 'design to code review', 'does my code match the design', 'design code parity', 'compare Penpot to my component', 'check this against Storybook', 'extract HTML/CSS from this selection', 'find design drift', 'design implementation diff'.
penpot-design-to-code-review — Close the design↔code bridge in the READ direction
1. Title + How it works
This skill reads a Penpot selection and compares it to the engineer's implemented code, then reports the drift. 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 reads tokens with penpotUtils.tokenOverview, extracts the design's HTML/CSS with penpot.generateMarkup(shapes, opts) and penpot.generateStyle(shapes, opts) (both invoked throughexecute_code), and produces a visual for side-by-side comparison with the separate export_shape tool. This skill is read-only on the canvas by default (mode: suggest) — it never mutates the design; it emits a report and, only on explicit request, proposes reconciliation steps. The code side (a React/Vue/etc. component file, or a rendered Storybook story / its DOM+CSS) is supplied by the user or by another MCP (filesystem, Storybook, a browser tool). If no code source is available, the skill degrades gracefully and compares the selection against the design-system tokens instead.
2. The One Rule That Matters Most
Never one-shot a review, and never mutate during one. Extract → normalize → map → diff → report, one logical execute_code call per step, with a read or export_shape between phases. A drift report that was assembled in a single blast is unverifiable and almost always wrong about token names. This is a suggest-mode skill: the canvas is not touched. The only thing you produce is a structured DRIFT report; reconciliation edits, if requested, are handed off to a write-capable skill (penpot-foundations for token work, penpot-rename-layers, or penpot-build-from-code for structural rebuilds) under their own checkpoints.
3. Penpot MCP Tool Reference
Full surface: see shared/penpot-mcp-tool-reference.md. The calls this skill leans on:
shape.fills, shape.strokes, shape.shadows, shape.borderRadius, geometry via shape.width/height/bounds
Produce a comparison image
export_shape (separate MCP tool, not JS) — one call per state board
Verify any unfamiliar signature with penpot_api_info (e.g. penpot_api_info("Page", "generateMarkup"), penpot_api_info("Shape", "tokens")) before relying on it.
4. Plugin API Essentials
Gotcha numbers refer to shared/plugin-api-gotchas.md. What bites this skill:
generateMarkup / generateStyle take an array of shapes, not a single shape, and accept an opts object. Confirm the exact opts keys with penpot_api_info("Penpot", "generateMarkup") / ("Penpot", "generateStyle") before passing options — guessing option names silently produces default output and you will wrongly conclude "no drift". Pass penpot.selection (already an array) directly.
#1 Style arrays are immutable item-by-item — fine for reading (shape.fills[0].fillColor); never attempt to "fix" a fill here — this skill does not mutate.
#5 width/height/parentX/parentY are read-only — read geometry for the diff; no resize()/setParentXY() here.
#2 Token application is async (~100 ms) — irrelevant to reading, but if a reconciliation hand-off applies a token, do not read it back in the same call (don't assert false failures).
shape.tokens is the authoritative map of which design tokens are bound to which properties. Prefer it over guessing tokens from raw hex. A raw value with no bound token is itself a drift finding ("design uses a hardcoded value, not a token").
generateStyle emits the design's notion of CSS — it does not know your code's class names or variables. Treat its output as the design's intent, then normalize both sides (see references/04) before diffing. Do not string-compare raw CSS.
Verify, don't guess. When generateMarkup/generateStyle return something unexpected, the first hypothesis is a wrong opts key or a non-array argument — re-check with penpot_api_info, not a retry with a different guess.
5. Token-Aware Brief Contract
Before extracting anything, restate the request as a contract. Act as a senior design-engineer who treats the design file as the spec of record and never accepts a hardcoded value where a token exists.
Context — what is being reviewed (a single component, a state set, a screen region) and for whom (which app/library, which framework the code is in).
Objective — single and specific: "Confirm <Button variant=primary size=md> in src/Button.tsx matches the Penpot selection, including hover/disabled states." Not "review the design."
Inputs —
the Penpot selection (resolve it; if empty, ask the user to select or name the board);
the code source: a file path / pasted component code, OR a Storybook story (URL or rendered DOM+CSS), OR explicitly none;
the token set in play (tokenOverview()), and which tier semantic tokens should resolve to.
Constraints — which side is authoritative for which dimension (usually: design authoritative for visual tokens, spacing, states; code authoritative for behavior/a11y semantics); tokens-only on the 4px grid; forbidden to mutate the canvas in this skill.
Acceptance Criteria (quantitative) —
every design property is classified as match / drift / code-only / design-only;
every color/spacing/radius/type drift names the token involved (or flags "no token bound");
color drifts report a hex-level delta and, where relevant, the WCAG contrast impact;
spacing drifts report the px delta and whether the value is on the 4px grid;
state coverage is reported as a matrix (design states × code states) with explicit gaps;
severity assigned to every finding (Critical / Major / Minor) per the rubric in §8.
6. Mandatory Workflow
Phase 0 — Discovery (read-only)
Goal: know what you're reviewing and what tokens exist before reading any style.
Steps:
high_level_overview (once per session).
execute_code: resolve penpot.selection; if empty, stop and ask the user to select the component/board. Return ids, names, types, count.
execute_code: penpotUtils.shapeStructure(sel[0], 4) for each top-level selected shape — capture the tree (names, types, nesting).
Confirm the code source with the user (file path / pasted code / Storybook / none).
Exit criterion: you can name the design's structure, its tokens, and the code source (or confirmed absence).
✋ Checkpoint: show the structure summary + token inventory + the code source you'll diff against. Ask the user to confirm scope and which side is authoritative for which dimension.
Phase 1 — Extract markup & style from the design
Goal: turn the selection into normalized HTML + CSS + a token map.
Steps: use scripts/extractMarkupStyle.js — one execute_code call that runs penpot.generateMarkup(sel, opts), penpot.generateStyle(sel, opts), walks shape.tokens for each node, and returns { markup, style, perNode:[{id,name,type,tokens,rawFills,rawSpacing,radius,typography}] }.
Exit criterion: you have the design's HTML, CSS, and a per-node token/raw-value map.
✋ Checkpoint: show the extracted markup + style (trimmed) and the token map. Confirm before mapping.
Phase 2 — Capture a comparison image
Goal: a visual the engineer can eyeball against the rendered code.
Steps: run scripts/exportForCompare.js to gather the shape id, bounds, and metadata; then call the export_shape MCP tool with 'selection' (or the captured id), format: png. For multi-state components, export each state board separately. Ask the engineer for a screenshot of the rendered code/story (or capture it via a browser MCP if available).
Exit criterion: design image(s) in hand, paired with the code render(s).
✋ Checkpoint: present side-by-side; let the engineer flag obvious visual deltas to prioritize.
Phase 3 — Map design ↔ source
Goal: establish the correspondence between design nodes/tokens/states and code elements/variables/props. See references/02.
Steps: parse the supplied code (props, CSS/var usage, state classes/variants); build a mapping table designNode → codeElement, designToken → codeVar, designState → codeState. If the code is a Storybook story, derive states from the story's args/controls (see references/03). If no code source: map design tokens to the design-system token set only and skip the code column (graceful degradation).
Exit criterion: a mapping table with every design property associated to a code counterpart or marked unmapped.
✋ Checkpoint: show the mapping table; correct mis-maps before diffing.
Phase 4 — Diff & emit DRIFT report
Goal: the deliverable.
Steps: run the comparison logic from references/04, assemble with scripts/generateDriftReport.js (structured output), classify each finding (match/drift/code-only/design-only), assign severity, and suggest a reconciliation for each non-match. Cite the WCAG number for any contrast-impacting color drift.
Exit criterion: a complete report covering tokens, structure, spacing, typography, and states, with severities and reconciliations.
✋ Checkpoint: present the report. If the user wants fixes applied, hand off to the appropriate write skill — do not mutate here.
7. Critical Rules
Read-only on the canvas. This skill never calls a mutating method. Reconciliation is a hand-off, gated by the downstream skill's own checkpoints.
Never string-compare raw CSS. Normalize both sides (units → px, colors → lowercase hex, shorthand expanded, properties sorted) before diffing. See references/04.
A hardcoded value with no bound token is a finding, even if it visually matches the code. Report it as Minor design-hygiene drift ("value not tokenized").
Name the token in every visual finding. Read shape.tokens first; only fall back to matching raw hex against tokenOverview() when no token is bound.
Spacing deltas state px and grid-alignment. Any value not a multiple of 4 is flagged regardless of whether code matches it.
Degrade, don't guess. No code source → compare against design-system tokens and say so in the report header; never fabricate the code side.
One execute_code per logical step, sequential, with a checkpoint between phases. Never batch extraction + mapping + diff.
Confirm generateMarkup/generateStyleopts with penpot_api_info before passing options.
Verify the selection is non-empty before any extraction; an empty selection silently yields empty markup.
State coverage is a matrix, not a checkbox. Report every (design state × code state) cell.
8. Domain Architecture
The DRIFT report schema (what generateDriftReport.js returns and what you render):
This shape is formalized in shared/report-schemas/drift-report.schema.json; include the derived top-level drift count (= summary.drift + summary.designOnly + summary.codeOnly), which the code-to-penpot-sync workflow branches on.
Severity rubric:
Severity
Definition
Critical
Functional/accessibility-affecting parity break: a state exists in design but not code (or vice versa) for an interactive element; a color drift drops contrast below WCAG 1.4.3 AA (4.5:1 / 3:1).
Major
Visible visual mismatch users will notice: wrong semantic token bound, spacing off by ≥ 8px, type size/weight mismatch, missing structural element (icon, label).
Minor
Hygiene / sub-perceptual: value not tokenized though it matches, ≤ 4px spacing delta, ordering/naming differences, optional decorative drift.
Dimensions diffed: token usage (which token, bound vs hardcoded), structure (node tree vs DOM tree), spacing (padding/gap/margin on the 4px grid), typography (family/size/weight/line-height/letter-spacing), radius/border, and state coverage.
9. Modes & Policies
See shared/modes-and-policies.md. This skill is suggest by default and stays there — it is an audit. It auto-applies nothing to the canvas. There is no safe-set for this skill because it performs no mutations; the "output" is the report. If the user asks to fix drift, the skill explicitly hands off: token swaps → penpot-foundations (apply-with-review), layer naming → penpot-rename-layers, structural/geometry changes → penpot-build-from-code. Each reconciliation is presented as a proposal with the source-of-truth side named; nothing is changed without that downstream skill's checkpoint.
10. State Management
See shared/state-management.md. RUN_ID slug example: d2c-2026-06-05-a. Ledger keys written via storage (this skill is read-only on the canvas, so it does not write setSharedPluginData unless the user opts into persisting the report):
storage.run.extracted — { markup, style, perNode } from Phase 1.
storage.run.mapping — the design↔code mapping table from Phase 3.
storage.run.report — the final DRIFT report object.
Resume: re-read storage.run; if absent (new session), re-run Phase 0 discovery and re-derive from shapeStructure + tokenOverview rather than trusting a stale report. Optionally mirror the report to setSharedPluginData(NS, "${RUN_ID}.driftReport", JSON.stringify(report)) only if the user wants it persisted in-file for handoff.
"Confirm scope + which side is authoritative for which dimension."
1 Extract
Trimmed markup + style, per-node token map
"Does this match what you expect the design to declare?"
2 Visual
export_shape PNG(s) paired with code render(s)
"Flag any obvious visual deltas to prioritize."
3 Mapping
design↔code mapping table
"Correct any mis-mapped node/token/state."
4 Report
Full DRIFT report (summary + findings + state matrix)
"Approve. Want reconciliation handed off to a write skill?"
12. Naming Conventions
See shared/naming-conventions.md. This skill reads names; it relies on them being semantic (button, label, icon, h1–h6) to map design nodes to DOM elements. Auto-generated names (Rectangle 12, Board) degrade mapping quality — if the selection is full of them, recommend running penpot-rename-layers first and note reduced structural-diff confidence in the report. Tokens are matched against the dot-notation taxonomy in shared/tokens-schema.json; token type strings are the exact Penpot spellings (color, spacing, borderRadius, fontSizes, etc.). Code variables (CSS custom props, JS theme keys) are normalized to that dot-notation before mapping (e.g. --color-action-primary-bg ↔ color.action.primary.bg).
13. Anti-Rationalization Table
Excuse the LLM makes
Why it's wrong
Deterministic countermeasure
"The CSS strings differ, so it's drift."
generateStyle emits 16px, code emits 1rem; #FFF vs #ffffff; shorthand vs longhand. Raw-string diff yields garbage findings.
Normalize both sides first (units→px, colors→lowercase 6-digit hex, expand shorthand, sort props). Diff the normalized maps only.
"Hex matches the code, so this property passes."
A matching hex with no bound token still breaks theming and governance — it silently won't switch in dark mode.
Read shape.tokens. If no token is bound, emit a Minor "value not tokenized" finding even when the hex matches.
"No Storybook/code was given, so I can't review."
The skill is required to degrade gracefully.
Switch to design-system-token mode: diff the selection against tokenOverview(), mark codeSource: "none" in the report header, and report only design-side findings.
"generateStyle came back nearly empty — design has barely any styling."
Far more likely you passed a single shape instead of the array, or a wrong opts key.
Re-check: pass penpot.selection (an array); verify opts with penpot_api_info. Re-extract before concluding "no drift."
"I'll just fix the token mismatch in Penpot while I'm here."
This is a suggest-mode read-only skill; mutating breaks the contract and skips the downstream checkpoint.
Do not mutate. Emit the reconciliation as a proposal and hand off to penpot-foundations/penpot-build-from-code.
"Spacing is 18px in both, so it matches — pass."
18 is off the 4px grid; matching code does not make it correct.
Flag any non-multiple-of-4 spacing as a finding (grid4px:false) regardless of code agreement; suggest the nearest grid token.
14. Helper Code Snippets
Resolve and summarize the selection (Phase 0):
const sel = penpot.selection || [];
return sel.length === 0
? { empty: true, hint: "Select the component/board to review, then re-run." }
: { count: sel.length, items: sel.map(s => ({ id: s.id, name: s.name, type: s.type })) };
Read which tokens are bound on a node vs raw values (Phase 1):