| name | penpot-design-md |
| description | Extract a DESIGN.md design-documentation file from a Penpot file: a portable markdown spec (YAML frontmatter with colors/typography/rounded/spacing/components token maps + prose sections: Overview, Colors, Typography, Layout, Shapes, Components, Do's & Don'ts, Iteration Guide, Known Gaps) in the design-md format, grounded in the file's real tokens, library assets, and sampled component styles — never invented. Read-only on the canvas; writes one local markdown file. Triggers: 'generate DESIGN.md', 'create a design.md', 'document the design system as markdown', 'export design guidelines', 'extract design documentation', 'design system spec file', 'make a style guide file from this design'. |
| disable-model-invocation | false |
| version | 0.2.0 |
| audiences | ["design-system","design-engineer","product-designer"] |
| mode-default | suggest |
| requires | ["shared/penpot-mcp-tool-reference.md","shared/plugin-api-gotchas.md","shared/tokens-schema.json","shared/naming-conventions.md","shared/state-management.md","shared/modes-and-policies.md","shared/visual-self-review.md"] |
penpot-design-md — Extract a DESIGN.md spec from a Penpot file
1. Title + How it works
This skill turns a Penpot file into a DESIGN.md: a single, portable markdown document that lets any human or
LLM reproduce the design system without opening Penpot — YAML frontmatter carrying the token maps
(colors, typography, rounded, spacing, components) plus prose sections that explain how
the system behaves (Overview, per-domain sections, Do's & Don'ts, Iteration Guide, Known Gaps).
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) — but
this skill performs no canvas mutation at all: it only reads (tokens, library assets, sampled
component styles, exported images) and writes one local markdown file on the agent's
filesystem. It is multimodal by design: the aesthetic prose (frontmatter description, Overview,
Do's & Don'ts) must be written after looking at real exports, not from token names alone.
2. The One Rule That Matters Most
Every value in the DESIGN.md must be traceable to something you read or saw in the file — never
invented, never "typical". A hex that appears in colors: was read from a token, a library
color, or a sampled shape fill. A padding in components: was read from a real component's
layout. A "Don't" was observed (the file never does it), not imagined. When something cannot be
extracted, it goes in Known Gaps — an honest gap beats a plausible fabrication, because
downstream agents will build from this file.
3. Penpot MCP Tool Reference
Full surface: shared/penpot-mcp-tool-reference.md. The calls this skill leans on (all read-only):
| Need | Call (inside execute_code unless noted) |
|---|
| Pages inventory | penpotUtils.getPages(), penpotUtils.getPageByName(name) |
| Token sets, themes, tokens | penpot.library.local.tokens → sets[], themes[], set.tokens[] (name, type, value, resolvedValue) |
| Library assets | penpot.library.local → components[], colors[], typographies[] |
| Component style sampling | component.mainInstance() then walk children: fills, strokes, borderRadius*, flex (padding/gap), text fontFamily/fontSize/fontWeight/lineHeight/letterSpacing, shape.tokens |
| Structure of a page/board | penpotUtils.shapeStructure(shape, depth) |
| Visual grounding | export_shape (separate MCP tool) on the cover, key component boards, and 1–2 representative screens |
Verify any unfamiliar signature with penpot_api_info before relying on it.
4. Plugin API Essentials
The gotchas that bite this skill (full list: shared/plugin-api-gotchas.md):
- #1 style arrays — read
fills[0].fillColor etc.; never attempt to write (this skill doesn't mutate).
- Variant reading is safe (#12 forbids mutation only) — read
variants.properties, variantComponents()[].variantProps freely to document the variant axes.
- Library color duplicates —
library.local.colors can hold several entries with the same short name (default, hover) belonging to different groups; disambiguate by color.path/group before mapping to semantic names, or fall back to value-based grouping.
- Text metrics live on the shape —
fontSize is a string in some builds; normalize to px numbers when emitting typography:.
mainInstance() of a heavy component can be slow — sample components one execute_code call per batch of ~5, not all in one blast.
- Verify unfamiliar signatures with
penpot_api_info before use.
5. Token-Aware Brief Contract
Before extracting, restate the request. Act as a senior design-systems documentarian: your
reader is another agent that will rebuild this system from your file alone.
- Context — which Penpot file/pages, what the product is, who consumes the DESIGN.md (humans, coding agents, both).
- Objective (single) — e.g. "Produce
DESIGN.md for the Pencil design system covering tokens, type, shape language, and the Button/Input/Modal component families."
- Inputs — the file's token sets + themes, library components/colors/typographies, the pages to sample, the output path for the file.
- Constraints — scope (whole system vs. named components); which theme is canonical (light/dark) and how to document the other; format is the design-md contract (see
references/02-designmd-format.md) — do not invent alternative section names.
- Acceptance Criteria (quantitative) —
- every
colors: entry has a hex read from the file, and its source (token/library color/sampled fill) is noted in the body or Known Gaps;
- every
typography: entry carries fontFamily/fontSize/fontWeight/lineHeight/letterSpacing read from a typography asset or a sampled text shape;
components: covers the agreed component list, each with values interpolated from {colors.*}/{typography.*}/{rounded.*} (no raw hex where a mapped color exists);
- Overview and Do's & Don'ts cite only observed behavior; ≥1 export was actually looked at before writing them;
- Known Gaps lists every domain that could not be extracted (responsive behavior, motion, elevation, …).
6. Mandatory Workflow
Visual grounding (mandatory): this skill's variant of shared/visual-self-review.md — you
cannot write the description, Overview, or Do's & Don'ts before exporting and looking at
the cover / key screens. Aesthetic prose written from token names alone is fabrication.
Phase 0 — Discovery (read-only)
- Goal: inventory what the file offers before deciding the extraction plan.
- Steps:
high_level_overview (once per session).
execute_code: run scripts/extractDesignData.js → pages, token sets (+ themes, active state), library components/colors/typographies, counts. Cache in storage.run.inventory.
- Decide with the user: scope (which components to document in depth), canonical theme, output path.
- Exit criterion: an inventory + an agreed scope and output path.
- ✋ Checkpoint: show the inventory summary and the proposed DESIGN.md scope (which sections will be rich, which will land in Known Gaps).
Phase 1 — Token & asset extraction
- Goal: the raw material for the frontmatter maps.
- Steps:
execute_code per group: dump color tokens (name → resolvedValue, per theme where sets differ), dimension/spacing/radius tokens, typography assets, library colors (deduped by group/path). Derive the semantic mapping (canvas, surface-*, ink-*, primary, hairline, semantic-*) from token names/tiers where they exist; from library-color groups and usage sampling where they don't (see references/03-inference.md).
- Exit criterion: draft
colors:, typography:, rounded:, spacing: maps with a source note per entry.
- ✋ Checkpoint: show the draft maps + anything unmapped. Confirm semantic names before they ossify into the doc.
Phase 2 — Component sampling
- Goal: the
components: map + the Components body section.
- Steps: for each in-scope component (batches of ≤5 per
execute_code): resolve mainInstance(), read background/text/border/radius/padding/typography and bound tokens; capture variant axes (variants.properties, variantProps) — states become separate entries (button-primary-hover, …). Interpolate values through the Phase 1 maps.
- Exit criterion: every in-scope component has a frontmatter entry + body bullets; unresolved values are flagged.
- ✋ Checkpoint: show 2–3 sample component entries for tone/format approval before doing the full list.
Phase 3 — Visual character pass
- Goal: the honest aesthetic prose.
- Steps:
export_shape the cover and 1–3 representative boards/screens. Look at them. Write: frontmatter description (one dense paragraph: dominant surfaces, accent usage, type character, shape language, rhythm), Overview (+ Key Characteristics bullets), and Do's & Don'ts (only rules the file itself demonstrates or its token structure implies).
- Exit criterion: prose grounded in what was seen; each Don't traceable to an observation ("no pill buttons anywhere", "single accent hue").
- ✋ Checkpoint: show the exports next to the drafted prose.
Phase 4 — Assemble, self-review, write
- Goal: the deliverable.
- Steps: assemble per
references/02-designmd-format.md (section order fixed); run the format self-check (frontmatter parses as YAML, every {ref} resolves to a frontmatter key, no raw hex in components: where a colors: entry exists, Known Gaps non-empty unless truly complete); write the file to the agreed path.
- Exit criterion: file written; self-check clean.
- ✋ Checkpoint: present the file (or its outline + key excerpts) for final approval.
7. Critical Rules
- Read-only on the canvas. No shape, token, or library mutation — ever. The only write is the local markdown file.
- No invented values. Every hex/px/weight is read from the file. Unknown → Known Gaps.
- Interpolate, don't repeat. Body prose and
components: reference {colors.x}/{typography.y}/{rounded.z}; raw values appear once, in the frontmatter maps.
- Look before you describe. No aesthetic prose before at least one
export_shape has been viewed (Phase 3).
- Themes are explicit. If the file has light/dark sets, document the canonical theme in the maps and the other as a delta subsection — never silently mix values from both.
- Dedupe library colors by group before semantic mapping; identical short names across groups are different colors.
- Curate, then disclose. A 100+-token file is summarized into semantic groups — say what was collapsed ("107 Global tokens → 14 semantic colors; full set names in Known Gaps") — no silent truncation.
- One extraction step per
execute_code call; component sampling in batches of ≤5.
- Variant axes are documentation gold — record
Property=Value matrices for components that have them; reading them is safe.
- The output must lint as the format — section order and frontmatter keys exactly per
references/02-designmd-format.md.
8. Domain Architecture
The DESIGN.md contract (summary — full spec in references/02-designmd-format.md):
--- (YAML frontmatter)
version, name, description # description = one dense aesthetic paragraph
colors: { semantic-name: "#hex", ... } # canvas/surface-*/ink-*/primary/hairline/semantic-*
typography: { style-name: {fontFamily,fontSize,fontWeight,lineHeight,letterSpacing}, ... }
rounded: { xs..pill: Npx }
spacing: { xxs..section: Npx }
components: { component-or-state: {backgroundColor,textColor,typography,rounded,padding,...}, ... }
---
## Overview # prose + **Key Characteristics** bullets
## Colors # Brand & Accent / Surface / Text / Semantic subsections, sources cited
## Typography # Font Family / Hierarchy / Principles / substitutes note
## Layout # Spacing System / Grid & Container / Whitespace Philosophy
## Elevation & Depth # shadows/borders ladder (or "flat by design")
## Shapes # Border Radius Scale + shape language
## Components # per component: **`name`** + purpose + property bullets with {refs}
## Do's and Don'ts # observed rules only
## Responsive Behavior # only if derivable from the file; else Known Gaps
## Iteration Guide # how a future agent should edit designs with this doc
## Known Gaps # everything not extractable — mandatory honesty section
Run ledger keys (see §10): inventory, maps, componentEntries, exportsViewed, gaps.
9. Modes & Policies
See shared/modes-and-policies.md. This skill is suggest and stays there: it never mutates
the canvas, so there is no safe-set and nothing to auto-apply. The single side effect — writing the
markdown file — happens at the Phase 4 checkpoint with the path the user approved in Phase 0.
Overwriting an existing DESIGN.md requires explicit confirmation (show a diff summary first).
10. State Management
See shared/state-management.md. RUN_ID slug example: designmd-2026-07-09-a. Session cache in
storage.run.* (inventory, maps, componentEntries, exports viewed, gaps). This skill is read-only,
so it does not write setSharedPluginData unless the user asks to persist a pointer to the
generated doc (designmd.lastGenerated = path + date). Resume: re-read storage.run; if gone,
re-run Phase 0 (extraction is cheap and idempotent).
11. User Checkpoints
| After phase | Artifacts shown | What we ask |
|---|
| 0 Discovery | Inventory (pages, sets, themes, assets) + proposed scope & output path | "Confirm scope, canonical theme, and where to write the file." |
| 1 Tokens | Draft frontmatter maps + unmapped values | "Approve the semantic names / flag mis-mappings." |
| 2 Components | 2–3 sample component entries | "Right tone and depth? Then I'll complete the list." |
| 3 Visual | Exports + drafted description/Overview/Do's & Don'ts | "Does this prose match what you see?" |
| 4 Assemble | The written file (outline + excerpts) | "Final approval — anything to add to Known Gaps?" |
12. Naming Conventions
See shared/naming-conventions.md. Frontmatter keys are kebab-case semantic names in the
design-md idiom (ink-muted, surface-2, display-xl, button-primary-hover) — map
Penpot's dot-notation tokens onto them and record the mapping (color.text.muted → ink-muted) in
the Colors section so the doc stays traceable back to the file. Component entries use the
component's kebab-cased name plus -hover/-pressed/-disabled/-selected state suffixes derived
from variant values.
13. Anti-Rationalization Table
| Excuse the LLM makes | Why it's wrong | Deterministic countermeasure |
|---|
| "The file has no tokens, so I'll write a typical palette." | Fabricated values poison every downstream consumer of the doc. | Stop. Sample real shape fills (references/03-inference.md), map what exists, put the rest in Known Gaps. |
| "I can describe the aesthetic from the token names." | color.primary = #00d1b8 doesn't tell you whether the design is dense or airy, flat or elevated. | Halt Phase 3 until ≥1 export_shape has been viewed. Prose cites what was seen. |
| "107 tokens is too many; I'll just list them all raw." | A raw dump is not documentation — the format demands curated semantic groups. | Curate into the semantic map, then disclose the collapse explicitly in Colors + Known Gaps. |
| "This component is obvious — a standard button, 8px radius." | "Standard" is fabrication; this file's button may be 6px with a hairline border. | Sample the actual mainInstance() before writing any component entry. |
| "Do's & Don'ts are generic best practices anyway." | The section documents THIS system's rules, not design folklore. | Each Do/Don't must trace to an observation (token structure, sampled styles, or a viewed export). |
| "Light and dark values differ; I'll average or pick per-property." | Mixed-theme values describe a design that doesn't exist. | Pick the canonical theme (Phase 0 decision); document the other as a delta subsection. |
14. Helper Code Snippets
Inventory (Phase 0 — or use scripts/extractDesignData.js):
const lib = penpot.library.local;
return {
pages: penpotUtils.getPages().map(p => p.name),
sets: (lib.tokens.sets || []).map(s => ({ name: s.name, active: s.active, count: (s.tokens||[]).length })),
themes: (lib.tokens.themes || []).map(t => ({ group: t.group, name: t.name })),
components: (lib.components || []).length, colors: (lib.colors || []).length,
typographies: (lib.typographies || []).map(t => t.name)
};
Dump color tokens with resolution (Phase 1):
const out = [];
for (const s of penpot.library.local.tokens.sets || []) {
if (!s.active) continue;
for (const t of s.tokens || []) if (t.type === "color")
out.push({ set: s.name, name: t.name, value: t.value, resolved: String(t.resolvedValue) });
}
return { count: out.length, tokens: out };
Sample one component's style (Phase 2):
const comp = penpot.library.local.components.find(c => c.name === "REPLACE-ME");
const root = comp.mainInstance();
const texts = penpotUtils.findShapes(s => s.type === "text", root).slice(0, 3);
return {
name: comp.name,
bg: root.fills && root.fills[0] ? root.fills[0].fillColor : null,
radius: root.borderRadius ?? null,
padding: root.flex ? [root.flex.topPadding, root.flex.rightPadding, root.flex.bottomPadding, root.flex.leftPadding] : null,
gap: root.flex ? [root.flex.rowGap, root.flex.columnGap] : null,
boundTokens: root.tokens || {},
text: texts.map(t => ({ family: t.fontFamily, size: t.fontSize, weight: t.fontWeight, lh: t.lineHeight, ls: t.letterSpacing, fill: t.fills && t.fills[0] && t.fills[0].fillColor })),
variantAxes: comp.isVariant && comp.isVariant() ? comp.variantProps : null
};
15. Reference Resources
penpot_api_info("TokenCatalog"), ("LibraryComponent", "mainInstance"), ("Variants"), ("LibraryTypography") — verify before Phase 1/2.
shared/penpot-mcp-tool-reference.md (tool surface), shared/plugin-api-gotchas.md (traps), shared/tokens-schema.json (tiers, Penpot token type strings).
- The format's canonical skeleton:
references/04-designmd-template.md (self-contained — no external corpus needed).
16. Supporting Files
References (progressive disclosure — load when that phase runs)
| File | Read during | Covers |
|---|
references/01-extraction.md | Phases 0–2 | What to read where: token sets/themes, library assets, component sampling recipes, variant axes, theme handling |
references/02-designmd-format.md | Phases 3–4 | The full DESIGN.md contract: frontmatter schema, section order, interpolation rules, tone, the self-check list |
references/03-inference.md | Phase 1 (token-poor files) | Deriving semantic maps without tokens: library-color groups, fill-frequency sampling, surface-ladder detection |
references/04-designmd-template.md | Phase 4 | The fill-in DESIGN.md skeleton (agnostic, self-contained) — copy, fill with extracted values, delete the guidance lines |
Scripts (paste into an execute_code call; replace placeholders)
| Script | Use for |
|---|
scripts/extractDesignData.js | Phase 0 — one-call read-only inventory: pages, sets, themes, assets, counts |
scripts/sampleComponentStyles.js | Phase 2 — batch-sample ≤5 components' resolved styles + bound tokens + variant axes |