---
name: pen-dev
description: Create high-quality visual designs — websites, app screens, dashboards, slides, marketing materials, social media graphics on the pen.dev canvas. Use when the task involves generating designs on the canvas.
---

# pen.dev

pen.dev is an infinite canvas design tool with nested object hierarchy.

## Quick reference

| Topic | When to use | Reference |
| --- | --- | --- |
| `.pen schema` | Learn the .pen schema that is used to represent. Required to read. | [pen-schema.md](./pen-schema.md) |
| `execute` | Learn how to use the `execute` MCP tool to generating designs on the canvas. Required to read. | [execute.md](./execute.md) |
| `components` | Learn how to use components and instances in .pen files. | [components.md](./guide/components.md) |
| `scripts-and-shaders` | Learn how to use scripts and shaders on the pen.dev canvas. | [scripts-and-shaders.md](./scripts-and-shaders.md) |
| `code` | Generating code from .pen files. | [code.md](./guide/code.md) |
| `design-system` | Composing screens with design system components. | [design-system.md](./guide/design-system.md) |
| `landing-page` | Designing landing pages and promotional websites. | [landing-page.md](./guide/landing-page.md) |
| `mobile-app` | Designing mobile apps. | [mobile-app.md](./guide/mobile-app.md) |
| `slides` | Designing presentation slides. | [slides.md](./guide/slides.md) |
| `table` | Working with tables and dashboards. | [table.md](./guide/table.md) |
| `tailwind` | Tailwind CSS v4 implementation. | [tailwind.md](./guide/tailwind.md) |
| `web-app` | Designing web apps. | [web-app.md](./guide/web-app.md) |

## General instructions

- Favor copying existing content and updating the copied content later, rather than generating new content.
- When creating new variables make sure you are not accidentally overwriting any existing design.
- User may ask for technical modifications like removing, moving, re-ordering, clearing, and copying objects/variables, or just ask questions. Only do what's requested and nothing more.
- pen.dev is a collaborative multiplayer environment: the document can change while you work, so the state you remember may be stale. If a node is missing or no longer matches what you expected, re-read instead of recreating it, and don't undo changes the user made in the meantime.
- When adding more content to a frame make sure the frame has the right layout, or is big enough to fit the new content. Resize the frame if necessary. There is no scrolling and the entire content should always be visible on the canvas.
- Place components at the top and your screens below, growing to the right and down.
- When creating new screens, represent each one as a top-level frame in `document`. Use `clip: true` on screen frames to prevent content overflow.
- Keep the document root clean: only page/screen frames, reusable component frames, and other major container frames belong directly under `document`. Never place text, icons, buttons, cards, rows, images, or decorative shapes directly in `document`.
- When the design has repeated UI, consider building those as reusable components first and then instancing them, so edits to the component propagate to every instance.
- Changes in the document are presented in real-time to the user. Make the changes in a logical order as a designer would.
- Minimize the time between the user requests and showing something on the screen. Users don't want to wait a long time before they see the progress.
- Use the canvas as part of your thinking. You don't need to preplan every little detail. Iteratively design using the document.
- Reasoning policy: keep your thinking brief. Do not plan the entire task up front in your reasoning. Think only about the immediate next step, make a tool call, and continue planning incrementally between tool calls.

- Do NOT use or think in CSS/HTML properties or behavior. pen.dev uses a custom format and has its own layout, rendering, and canvas behavior. pen.dev has similar concepts and naming, but it's not the same as CSS/HTML.
- If a property is not present in the .pen schema, it's not supported. Find a different way to achieve the same visual effect.
- Do NOT use: alignItems baseline/stretch, margin, percentage size. These values are not supported and will cause an error.

Verify each section immediately after you are done with it. Don't wait until the end of the whole generation.
Create a checklist to evaluate the design after you create it. Make sure to check for the following:
- Layout it not collapsed or broken.
- Content is not clipped outside the frame. Resize the container frames to fit the content if necessary.
- Color contrast on text, objects, and background is sufficient.
- Objects are properly aligned and spaced.
- Intention and understanding of the pen.dev schema matches the visuals.
After reviewing the design, do NOT delete it to make changes. If you want to fix it, always make direct updates to the existing objects.

### Style

- Don't create repetitive styles and grids. Add some unique elements and layout to make the design feel more interesting.
- Avoid wrapping every element in its own box or card. This is a common AI habit that makes designs look generic. Use a container only when it has a real structural or functional purpose.
- Avoid excessive gradients, shadows, and rounded corners unless requested or when part of the brand identity. Be refined and intentional with effects and decorations
- All Google fonts are available.
- Never draw logos, illustrations, mascots, or other freeform artwork yourself out of paths and shapes - hand-built drawings always look bad. Whenever the user asks you to draw something, or a design needs a logo, illustration, or decorative graphic, use the `Generate` function with `type: "svg"` on a frame instead.

#### Style archetypes

The `get_style` MCP tool provides ready-made visual style archetypes with configurable fonts, colors, and imagery. Use one when the user has no specific brand or style direction. Load a style with `get_style({ name: "..." })`; if the style requires params, the tool returns the options to choose from — call it again with all params filled in.

Available styles:

- Aerial Gravitas
- Anchored Ribbon Grid
- Artisan Editorial
- Blueprint Technical
- Centered Device Cascade
- Centered Serif List
- Cinematic Alternating
- Cinematic Device Column
- Color Block Stack
- Dark Centered Platform
- Editorial Landscape Stack
- Editorial Scientific
- Gradient Prompt Stack
- Illustrated Ribbon Stack
- Illustrated Warm
- Inline Friendly
- Modular Bento Showcase
- Monumental Editorial
- Narrative Illustrated
- Product Data Grid
- Product Demo
- Saturated Code Bridge
- Soft Bento
- Spatial Plus
- Split Inverse Showcase
- Zigzag Bold Split

### Objects

- All object coordinates are defined relative to the parent’s top-left corner.
- Use a coordinate system where `x` increases to the right and `y` increases downward.
- Objects rotate counter-clockwise around the top-left corner of their bounding box.
- Only `frame` and `group` types can have children. Shapes, text, and other nodes cannot have children.

- Properties do not cascade from parents to children. Every node is independent and must have all necessary properties specified.
- Exclude default property values unless they are overriding a non-default value inside an instance.

- Avoid duplicating the same dimension value across multiple sibling elements. If several children need to match their parent's width or height, use `fill_container` on each rather than hardcoding the parent's size repeatedly.
- Explicitly specify `width` and `height` for shapes and other nodes whose size is not otherwise determined by layout or text behavior.
- For layout-driven nodes, prefer `fit_content` and `fill_container` when appropriate instead of hardcoded numeric sizes.
- Set children to `fill_container` to distribute them evenly within their parent. Use the `gap` property on the parent to add gaps between children.

- Use `justifyContent: "center"` and `alignItems: "center"` on the parent to center its children both vertically and horizontally.
- For text, follow `textGrowth` rules: do not set `width` or `height` unless `textGrowth` requires them.
- Use `textAlign` or `textAlignVertical` to align the text within the text bounding box. `textAlign` has a visible effect when `textGrowth` is `fixed-width` or `fixed-width-height`. `textAlignVertical` has a visible effect only when `textGrowth` is `fixed-width-height`.
- Setting `textAlign` or `textAlignVertical` will not change the position of the text bounding box. Use flexbox layout to align the object.
- Use `textGrowth` to define text wrapping and bounding box sizing. When not specified, the default value is `"auto"`.
- Possible `textGrowth` values:
  - `auto`: `width` and `height` are always derived from the text content, any `width` or `height` you set is ignored. Never does line wrapping, text will always be on a single line.
  - `fixed-width`: the `width` node property MUST be specified, `height` is calculated from the text content. Does line wrapping based on the object's bounding box width.
  - `fixed-width-height`: both `width` and `height` node property MUST be specified. Does line wrapping based on the object's bounding box width. Text content will vertically overflow.
- Only use `fixed-width-height` when you need to override the height of the text box. Prefer `fixed-width` with `fill_container` for text that needs to adapt to the parent container size.
- If you want to wrap lines, you HAVE TO set the `textGrowth` to either `fixed-width` or `fixed-width-height`.
- Never guess text dimensions, always rely on text wrapping and flexbox layout to size and position text. Any dimension guess for text will result in visual bugs.
- Use the `lineHeight` property on text as a ratio relative to the font size: `0.0` means 0%, and `1.0` means 100%. If not specified, the font’s default line height will be applied.

- Text has no `fill` by default and will be invisible. You MUST set the `fill` property on text objects to make them visible. Emoji requires `fill` as well.
- To reference a variable, use a string value with a `$` prefix (`fill: "$primary-color"`, `gap: "$spacing-small"`)
- `width` and `height` do not support percentage or viewport CSS values. Never use values like `"70%"`, `"100%"`, `"50vh"`, or `"calc(...)"` or even `value + "%"`. If you need proportion-based sizing that's not uniform from the layout, you need to use fixed pixel values.
- `fill` can be set on wrapping containers to add a background color, gradient, or an image.

### Flexbox Layout

- `layout` and `padding` is only accessible on `frame` type. Do NOT set `layout` and `padding` on other types of nodes.
- **Prefer dynamic sizing over hardcoded values.** Use `fill_container` or `fit_content`, rather than repeating the parent's or children's pixel value. This makes designs more maintainable.
- Always prefer flexbox layout; only use `layout: "none"` when truly necessary.
- x and y properties are completely ignored when the node is in layout. Do NOT set x/y on a child unless the parent has layout: "none" or the node has layoutPosition: "absolute"
- Only use explicit numerical sizes in rare cases when it cannot be inferred from the layout.
- To align and distribute objects within a container with flexbox, wrap them in a parent object that has a `layout` property.
- Frames always default to `horizontal` direction and `fit_content` sizing.
- Padding affects ALL children uniformly - it creates space between the container's edges and its children.
- To offset an individual child in flexbox, wrap it in a flexbox frame with padding.
- Flexbox layout is single-axis only with no item wrapping. For grid-like layouts, manually create separate row frames.
- A parent cannot be sized by its children using `fit_content` if all direct children are sized by the parent using `fill_container`. This creates circular dependency. Don't rely on the fallback value to resolve circular dependency.

**Antipattern**
```js
// WRONG: percentage values are not supported
Insert(parent,{type:"frame",width:"100%",height:`${100/count}%`})

// WRONG: padding on text is not supported, use a wrapping frame instead
Insert(parent,{type:"text",content:"text",fontSize:12,padding:12})

// WRONG: Collapses to 0 width. Parent defaults to fit_content. Child tries to fill it.
badParentId = Insert(screen, {type: "frame", layout: "vertical"});
Insert(badParentId, {type: "text", textGrowth: "fixed-width", width: "fill_container", content: "..."});
```

#### Text Sizing

Text sizing depends on whether the parent or the text content controls the size.

**Parent defines size** - parent must have flexbox layout and determines the size. Use `textGrowth:"fixed-width"` + `fill_container` (headings, descriptions, paragraphs):

```js
sectionId=Insert(parent,{type:"frame",name:"Header",layout:"vertical",width:400,gap:12,x:200,y:200})
Insert(sectionId,{type:"text",name:"Title",textGrowth:"fixed-width",width:"fill_container",fontFamily:"Inter",content:"Dashboard",fontSize:24,fill:"$text-primary"})
Insert(sectionId,{type:"text",name:"Subtitle",textGrowth:"fixed-width",width:"fill_container",fontFamily:"Inter",content:"Manage your account settings",fontSize:14,fill:"$text-secondary"})
```

**Text defines size** - default `auto`, no width/height (button labels, tags, badges):

```js
btnId=Insert(parent,{type:"frame",name:"Button",padding:12,gap:8})
Insert(btnId,{type:"text",name:"Label",fontFamily:"Inter",content:"Submit",fontSize:14,fill:"$text-primary"})
```

**Antipattern** - using pixel dimensions when layout can handle it:

```js
containerId=Insert(parent,{type:"frame",name:"Button",width:200,height:100,padding:12})
// WRONG: parent has layout, use fill_container instead of pixel width
Insert(containerId,{type:"text",content:"abc",textGrowth:"fixed-width",width:320,fontSize:14})
// WRONG: missing textGrowth after specifying width
Insert(containerId,{type:"text",content:"abc",width:100,fontSize:14})
```

### Using placeholders

- Any new, copied, or modified root frame MUST have `placeholder: true` for the entire duration of the work on it.
- Once you start working inside a placeholder node, finish it before unsetting the flag.
- Remove `placeholder: true` as soon as the frame is done - don't wait until all screens are finished.

### SVG Path

- Always set an explicit `viewBox: [x, y, width, height]` on a path. It defines the region of the SVG coordinate space that maps onto the node's full box, and lets you keep the path's raw coordinates while still controlling placement via the node's `x`/`y`/`width`/`height`.
- The viewBox region is stretched to fill the node's width/height.

### Graphs

- Always prefer bar charts and charts that can be built with simple layout configurations that the .pen format supports.
- Don't use absolutely positioned elements over the chart, as they won't align correctly.
- Don't manually match labels to bar positions and sizes. Rely on layout to position labels and bars correctly.
- When creating donut charts, always use `fill` color with `innerRadius` size to create the donut shape.
- Line charts cannot be easily built because the layout system cannot position individual points.
