| name | penpot-design |
| description | Design and manipulate Penpot projects using the Penpot MCP Plugin. Use when working with Penpot designs including creating shapes, boards, layouts, styling, components, generating CSS/HTML from designs, or any design task when Penpot MCP is connected. Triggers on mentions of "Penpot", design manipulation requests with connected MCP, or design-to-code workflows. |
Penpot Design Skill
Create, edit, and manipulate designs in Penpot using the connected MCP plugin.
Prerequisites
User must have Penpot MCP Plugin connected to their Penpot project.
Available Tools
| Tool | Purpose |
|---|---|
execute_code |
Run JavaScript in Penpot plugin context |
penpot_api_info |
Get API docs for types/members |
export_shape |
Export shapes as PNG/SVG for inspection |
import_image |
Import images as Rectangle fills |
Core Objects
penpot // Main API: selection, root, library, fonts, viewport
penpotUtils // Helpers: findShape, findShapes, shapeStructure, setParentXY
storage // Persistent storage across tool calls
Critical Rules
1. Child Ordering is REVERSED in Flex Layout
For dir="column" or dir="row", the children array order is reversed relative to visual order. First in array = visually last.
2. Use insertChild, NOT appendChild
// ✅ CORRECT - predictable ordering
parent.insertChild(parent.children.length, shape);
// ❌ BROKEN - unpredictable placement
parent.appendChild(shape); // Don't use for non-flex boards!
Exception: For flex layout boards, use board.appendChild(shape) which inserts at the visual end.
3. Position Properties
shape.x = 100; // ✅ Writable - absolute page coordinates
shape.y = 200; // ✅ Writable
shape.parentX; // ❌ READ-ONLY
shape.parentY; // ❌ READ-ONLY
// To set relative position:
penpotUtils.setParentXY(shape, relativeX, relativeY);
4. Resize and Dimensions
shape.width; // ❌ READ-ONLY
shape.height; // ❌ READ-ONLY
shape.resize(200, 100); // ✅ Use method to change size
5. Text Sizing
text.fontSize = '24'; // ✅ Changes text size
text.resize(200, 100); // ❌ Only changes bounding box, not font
text.growType = 'auto-width'; // ✅ Always set after resize for auto-sizing
6. Store Selection Immediately
storage.selection = [...penpot.selection]; // Selection can change!
Workflow
1. Explore Design Structure
// Page overview (depth 3)
return penpotUtils.shapeStructure(penpot.root, 3);
// Store current selection
storage.selection = [...penpot.selection];
return storage.selection.map(s => ({ id: s.id, name: s.name, type: s.type }));
2. Find Elements
// By name
const shape = penpotUtils.findShape(s => s.name === 'Button');
// By type
const texts = penpotUtils.findShapes(s => s.type === 'text', penpot.root);
// Images (including fill images)
const images = penpotUtils.findShapes(
s => s.type === 'image' || s.fills?.some(f => f.fillImage),
penpot.root
);
// All boards
const boards = penpotUtils.findShapes(s => s.type === 'board', penpot.root);
3. Create Shapes
// Rectangle
const rect = penpot.createRectangle();
rect.name = 'Card Background';
rect.x = 100; rect.y = 100;
rect.resize(300, 200);
rect.fills = [{ fillColor: '#FFFFFF' }];
rect.borderRadius = 12;
// Text
const text = penpot.createText('Hello World');
text.x = 120; text.y = 120;
text.fontSize = '24';
text.fontFamily = 'Inter';
text.growType = 'auto-width';
// Board (frame/artboard)
const board = penpot.createBoard();
board.name = 'Card';
board.x = 0; board.y = 0;
board.resize(400, 300);
board.fills = [{ fillColor: '#FFFFFF' }];
// Ellipse
const ellipse = penpot.createEllipse();
ellipse.resize(100, 100);
// Path
const path = penpot.createPath();
4. Build Hierarchy
CRITICAL: Add background shapes FIRST, then foreground shapes. Z-order = array order.
const card = penpot.createBoard();
card.resize(300, 200);
// 1. Background first (will be behind)
const bg = penpot.createRectangle();
bg.resize(300, 200);
bg.fills = [{ fillColor: '#F5F5F5' }];
card.insertChild(card.children.length, bg);
// 2. Content on top (will be in front)
const title = penpot.createText('Title');
title.fontSize = '20';
card.insertChild(card.children.length, title);
// Position relative to parent
penpotUtils.setParentXY(bg, 0, 0);
penpotUtils.setParentXY(title, 20, 20);
5. Flex Layout
const container = penpot.createBoard();
container.resize(400, 0);
// Add flex layout (use helper to preserve child order)
const flex = penpotUtils.addFlexLayout(container, 'column');
flex.rowGap = 16;
flex.columnGap = 16;
flex.topPadding = 24;
flex.rightPadding = 24;
flex.bottomPadding = 24;
flex.leftPadding = 24;
flex.alignItems = 'stretch';
flex.justifyContent = 'start';
// For flex boards, appendChild adds at visual end
container.appendChild(item1);
container.appendChild(item2);
// Child sizing within flex
item1.layoutChild.horizontalSizing = 'fill';
item1.layoutChild.verticalSizing = 'auto';
Flex Layout Properties:
dir: 'row' | 'column' | 'row-reverse' | 'column-reverse'alignItems: 'start' | 'center' | 'end' | 'stretch'justifyContent: 'start' | 'center' | 'end' | 'stretch' | 'space-between' | 'space-around' | 'space-evenly'rowGap,columnGap: numbertopPadding,rightPadding,bottomPadding,leftPadding: number
6. Styling
Fills:
// Solid color
shape.fills = [{ fillColor: '#3B82F6', fillOpacity: 1 }];
// Gradient
shape.fills = [{
fillColorGradient: {
type: 'linear',
startX: 0, startY: 0,
endX: 1, endY: 1,
width: 1,
stops: [
{ color: '#3B82F6', offset: 0 },
{ color: '#8B5CF6', offset: 1 }
]
}
}];
Strokes:
shape.strokes = [{
strokeColor: '#E5E7EB',
strokeWidth: 1,
strokeAlignment: 'center', // 'inner' | 'outer'
strokeStyle: 'solid' // 'dotted' | 'dashed'
}];
Shadows:
shape.shadows = [{
style: 'drop-shadow', // or 'inner-shadow'
color: { color: '#000000', opacity: 0.1 },
offsetX: 0,
offsetY: 4,
blur: 12,
spread: 0
}];
Border Radius:
shape.borderRadius = 8; // All corners
// Or individual:
shape.borderRadiusTopLeft = 8;
shape.borderRadiusTopRight = 8;
shape.borderRadiusBottomRight = 0;
shape.borderRadiusBottomLeft = 0;
Opacity & Blend:
shape.opacity = 0.8;
shape.blendMode = 'multiply'; // 'normal' | 'darken' | 'screen' | etc.
7. Text Styling
const text = penpot.createText('Hello');
text.fontSize = '16';
text.fontFamily = 'Inter';
text.fontWeight = '600';
text.fontStyle = 'normal'; // or 'italic'
text.lineHeight = '1.5';
text.letterSpacing = '0';
text.align = 'center'; // 'left' | 'right' | 'justify'
text.verticalAlign = 'center'; // 'top' | 'bottom'
text.textTransform = 'uppercase'; // 'lowercase' | 'capitalize'
text.textDecoration = 'underline'; // 'line-through'
text.direction = 'ltr'; // 'rtl'
text.growType = 'auto-width'; // 'auto-height' | 'fixed'
// Text color
text.fills = [{ fillColor: '#1F2937' }];
8. Z-Order Methods
shape.bringToFront(); // Move to top
shape.sendToBack(); // Move to bottom
shape.bringForward(); // Move up one level
shape.sendBackward(); // Move down one level
shape.setParentIndex(0); // Set exact position (0-based)
Design-to-Code
Generate CSS
// CSS for selected elements
return penpot.generateStyle(penpot.selection, {
type: 'css',
withPrelude: true,
includeChildren: true
});
Generate HTML/SVG
return penpot.generateMarkup(penpot.selection, { type: 'html' });
// or
return penpot.generateMarkup(penpot.selection, { type: 'svg' });
Design-to-Code Workflow
- Select target element(s) in Penpot
- Use
export_shapeto visually verify - Generate CSS with
generateStyle - Generate markup with
generateMarkup - Combine into working code
CRITICAL: Never assume values. Strictly adhere to the design. Use black/white defaults only when information is genuinely missing.
Components & Libraries
// Access libraries
const local = penpot.library.local;
const connected = penpot.library.connected;
// Find component
const btn = local.components.find(c => c.name.includes('Button'));
// Create instance
const instance = btn.instance();
instance.x = 100;
instance.y = 100;
// Access colors
const primary = local.colors.find(c => c.name === 'Primary');
// Access typography
const heading = local.typographies.find(t => t.name === 'Heading');
// Create library assets
const newColor = local.createColor();
newColor.name = 'Brand Blue';
newColor.color = '#3B82F6';
const newTypo = local.createTypography();
newTypo.name = 'Body';
newTypo.fontSize = '16';
newTypo.fontFamily = 'Inter';
Visual Inspection
Always verify designs visually:
// Use export_shape tool to see the result
// export_shape({ shapeId: shape.id, format: 'png' })
// or use 'selection' for currently selected shape
Common Patterns
See references/patterns.md for:
- Card components
- Button variants
- Navigation bars
- Form fields
- Grid layouts
- Modal dialogs
API Quick Reference
See references/api-quick-ref.md for:
- All shape types and properties
- Layout system details
- Complete styling reference
- Library management
Shape Types
| Type | Description |
|---|---|
Board |
Container/frame, supports layouts |
Rectangle |
Basic rectangle shape |
Ellipse |
Circle/ellipse shape |
Text |
Text element |
Path |
Vector path |
Group |
Non-layout container |
Boolean |
Boolean operations result |
Image |
Image element (legacy) |
SvgRaw |
Raw SVG content |