| name | penpot-workflow |
| description | Manage design work in Penpot — create projects, organize pages, build components, export assets, review designs visually, and handle design-to-code handoff. Use when asked to create designs, manage Penpot projects, build components, export tokens, set up a new design project, review a design, take screenshots, or show what something looks like.
|
Penpot Workflow
Step-by-step workflows for managing design work in Penpot via the pi-penpot extension.
Project Setup
When starting a new design project:
- Create the Penpot project with a clear, matching name (same as the code repo)
- Set up standard pages:
Design System — shared components, tokens, and styles
Wireframes — low-fidelity layouts and flows
- One page per major screen or user flow (e.g.,
Dashboard, Settings, Onboarding)
- Define foundations first:
- Color palette (primary, secondary, neutral, semantic: success/warning/error/info)
- Typography scale (headings h1-h6, body, caption, label, mono)
- Spacing scale (aligned with Tailwind: 4px base unit)
- Border radius tokens
- Shadow/elevation tokens
Shape Creation
Creating shapes with full styling
Use penpot_page tool actions. All shapes support fills, opacity, and rotation on creation.
Rectangles and frames also support r1-r4 for border radius on creation:
penpot_page add-rectangle fileId=X pageId=Y x=0 y=0 width=200 height=100
fills=[{fillColor: "#1E293B", fillOpacity: 1}]
r1=12 r2=12 r3=12 r4=12
Text supports fontSize, fontWeight, fontFamily, fontColor on creation:
penpot_page add-text fileId=X pageId=Y x=0 y=0 width=200 height=40
text="Hello World"
fontSize="24" fontWeight="700" fontFamily="sourcesanspro" fontColor="#FFFFFF"
Available font families
sourcesanspro — Source Sans Pro (default, always available)
- Custom fonts can be uploaded via
create-font-variant
Font size and weight are STRINGS
Always pass fontSize and fontWeight as strings: "24" not 24, "700" not 700.
Shape Styling (modify-shape)
After creation, use modify-shape to add advanced styling. First-class params:
Border Radius
penpot_page modify-shape fileId=X pageId=Y shapeId=Z r1=16 r2=16 r3=16 r4=16
r1 = top-left, r2 = top-right, r3 = bottom-right, r4 = bottom-left
Shadows
penpot_page modify-shape fileId=X pageId=Y shapeId=Z
shadow=[{
style: "drop-shadow",
color: {color: "#000000", opacity: 0.3},
offsetX: 0, offsetY: 4, blur: 12, spread: 0
}]
- Styles:
drop-shadow, inner-shadow
- UUID is auto-generated if not provided
- Multiple shadows supported (array)
Blur
penpot_page modify-shape fileId=X pageId=Y shapeId=Z
blur={type: "layer-blur", value: 4}
- Types:
layer-blur, background-blur
- UUID is auto-generated
Strokes
penpot_page modify-shape fileId=X pageId=Y shapeId=Z
strokes=[{strokeColor: "#7C3AED", strokeOpacity: 0.5, strokeWidth: 2, strokeAlignment: "inner"}]
- Alignment:
inner, center, outer
- Style (via attrs):
solid, dotted, dashed, mixed, none
Text Content (re-style existing text)
penpot_page modify-shape fileId=X pageId=Y shapeId=Z
textContent={
type: "root",
children: [{type: "paragraph-set", children: [{type: "paragraph", children: [{
text: "Updated text",
fontFamily: "sourcesanspro",
fontSize: "32",
fontWeight: "700",
fontStyle: "normal",
fillColor: "#FFFFFF",
fillOpacity: 1
}]}]}]
}
Generic attrs (any shape attribute)
Use attrs as a catch-all for any shape property not covered above:
penpot_page modify-shape fileId=X pageId=Y shapeId=Z
attrs={hidden: true, blocked: false}
Design Workflow — Best Practices
Step-by-step for polished designs
- Create shapes with basic geometry + fills + border radius
- Style shapes with modify-shape: shadows, strokes, blur
- Create text with proper fontSize/fontWeight/fontColor from the start
- Re-style text with textContent on modify-shape if needed later
- Batch work — create all shapes first, then style them (reduces API calls)
Performance tips
- Create shapes in rapid succession (each is one API call)
- Use modify-shape to batch multiple styling changes on one shape (fills + strokes + shadow in one call)
- Always get file features from
penpot get-file before manual API calls
What works with the extension tools
| Feature | Create | Modify | Notes |
|---|
| Position & size | ✅ | ✅ | x, y, width, height |
| Fills | ✅ | ✅ | Array of fill objects |
| Border radius | ✅ | ✅ | r1, r2, r3, r4 params |
| Shadows | ❌ | ✅ | shadow param, UUID auto-generated |
| Blur | ❌ | ✅ | blur param, UUID auto-generated |
| Strokes | ✅ | ✅ | strokes param |
| Text styling | ✅ | ✅ | fontSize/fontWeight/fontColor on create; textContent on modify |
| Opacity | ✅ | ✅ | 0-1 number |
| Rotation | ✅ | ✅ | degrees |
Visual Review
When you need to review your own work or the user asks you to review a design, capture actual rendered screenshots from Penpot's viewer.
When to do this
- After creating or modifying designs — always screenshot to verify the result
- When the user says "review", "show me", "how does it look", "screenshot"
- Before handoff — capture final state for documentation
Workflow
1. Get the file and page IDs
penpot get-file fileId=<fileId>
2. Create a share link
penpot create-share-link fileId=<fileId> pages=[<pageId1>, <pageId2>] whoInspect="all"
Share links are required — Penpot's viewer won't render without either authentication or a share token.
3. Screenshot each page with Playwright
The Penpot viewer URL format is:
https://penpot.e9n.dev/#/view?file-id=<fileId>&page-id=<pageId>§ion=interactions&index=0&share-id=<shareId>
Use Playwright to screenshot each page URL. Penpot is a complex ClojureScript SPA — wait 6+ seconds after navigation for it to render.
Cleanup: Always close the Playwright browser after capture to prevent leaking Chromium processes:
await browser.close();
Save screenshots to /tmp/penpot-<page-name>.png, then read them to view inline. After viewing, delete the temporary file to avoid accumulating disk usage:
rm /tmp/penpot-<page-name>.png
4. Review what you see
After viewing the screenshot, evaluate:
- Layout — is there wasted space? Are elements aligned to a grid?
- Visual hierarchy — can you immediately tell what's important?
- Consistency — do similar elements (cards, tags, buttons) share the same styling?
- Completeness — are there missing states, empty areas, placeholder content?
- Accessibility — text contrast, touch target sizes, focus indicators
- Polish — border radius consistency, shadow consistency, proper spacing
Component Creation
Follow atomic design methodology:
Atoms (smallest building blocks)
- Buttons (primary, secondary, ghost, destructive — each with default/hover/active/disabled/focus)
- Inputs (text, textarea, select, checkbox, radio, toggle)
- Labels, badges, tags
- Icons (use Lucide as base set)
- Avatar, tooltip, separator
Molecules (composed atoms)
- Form fields (label + input + helper text + error message)
- Search bar (input + icon + button)
- Card (container + content slots)
- Menu item (icon + label + shortcut badge)
Organisms (composed molecules)
- Navigation (sidebar, topbar, breadcrumbs)
- Data tables (headers + rows + pagination + filters)
- Modals/dialogs (overlay + card + action buttons)
- Forms (multiple form fields + submit action)
Naming Convention
Use slash-separated categories: category/component-name/variant
buttons/primary/default
buttons/primary/hover
forms/text-input/filled
navigation/sidebar/collapsed
States & Variants
Every interactive component needs these states:
- Default — resting state
- Hover — mouse over (desktop)
- Active/Pressed — being clicked/tapped
- Focus — keyboard focus (visible focus ring, WCAG required)
- Disabled — non-interactive
- Loading — async operation in progress (skeleton or spinner)
- Error — validation failure
Responsive Design
Design at these breakpoints (aligned with Tailwind):
- Mobile: 375px (iPhone SE baseline)
- Tablet: 768px
- Desktop: 1280px
- Wide: 1536px
Use Penpot's grid and layout features:
- Auto-layout for flex-like behavior
- Grid components for dashboard layouts
- Constraints for responsive positioning
Design-to-Code Handoff
When preparing designs for developer handoff:
-
Export design tokens as JSON:
{
"colors": { "primary-500": "#7c6ff0", ... },
"spacing": { "1": "4px", "2": "8px", ... },
"typography": { "heading-1": { "size": "36px", "weight": 700, "lineHeight": 1.2 } },
"radii": { "sm": "4px", "md": "8px", "lg": "12px" },
"shadows": { "sm": "0 1px 2px rgba(0,0,0,0.05)", ... }
}
-
Map components to shadcn-svelte — document which Penpot component maps to which shadcn component and what customization is needed
-
Annotate specs — add notes on each page for:
- Spacing between elements
- Interaction behavior (transitions, animations)
- Edge cases (empty states, error states, loading states)
- Accessibility notes (ARIA labels, focus order)
-
Export assets — SVGs for icons and illustrations, optimized images
Penpot API Patterns
Use the pi-penpot extension's three tools:
penpot — org-level: projects, files, teams, libraries, webhooks, snapshots, share links
penpot_page — design: pages, shapes (create/modify/delete/move), components
penpot_comment — collaboration: comment threads and replies
Critical API knowledge
- Always get the file first —
penpot get-file returns pageIds and file features
- File features must match — features from
get-file must be passed back in update-file
- Transit+JSON encoding — the extension handles this automatically for all write operations
- UUIDs auto-generated — shadow and blur IDs are auto-generated when not provided
- camelCase in params, kebab-case internally — the extension converts automatically
Checklist Before Handoff