pencil

Design UI in .pen files using Pencil MCP (pencil.dev). Use this skill when creating designs, screens, dashboards, landing pages, components, or editing .pen files. Covers batch_design operations, component patterns, layout systems, style guides, and visual verification workflows.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "pencil" with this command: npx skills add nickbreaton/.opencode/nickbreaton-opencode-pencil

Pencil MCP Design Skill

This skill provides comprehensive guidance for designing user interfaces using the Pencil MCP tools. Pencil uses .pen files — a JSON-based design format with flexbox layout, components, variables, and theming support.


Quick Reference: Essential Workflow

1. pencil_get_editor_state       → Get current file, selection, schema
2. pencil_get_guidelines         → Load topic-specific rules (design-system, landing-page, table)
3. pencil_get_style_guide_tags   → Get available style tags (if designing from scratch)
4. pencil_get_style_guide        → Get visual direction with 5-10 tags
5. pencil_get_variables          → Read design tokens ($--colors, $--fonts, etc.)
6. pencil_batch_get              → Inspect existing nodes and components
7. pencil_batch_design           → Create/modify design (max 25 ops per call)
8. pencil_get_screenshot         → Verify visual output

Core Concepts

The .pen File Structure

A .pen file is a JSON document containing:

  • children: Array of top-level frames (screens, components)
  • variables: Design tokens (colors, fonts, radii)
  • themes: Theme axes and values
  • fonts: Custom font definitions

Node Types

TypeDescriptionKey Properties
frameContainer with layoutlayout, gap, padding, fill, clip
textText contentcontent, fontSize, fontFamily, fill, textGrowth
rectangleShapefill, stroke, cornerRadius
ellipseOval/circlefill, stroke
icon_fontIcon from font seticonFontFamily, iconFontName, fill
refComponent instanceref (points to reusable component), descendants
groupLogical groupingchildren, optional layout

Flexbox Layout System

Frames use flexbox by default. Key properties:

{
  layout: "vertical" | "horizontal" | "none",  // none = absolute positioning
  gap: 16,                                      // spacing between children
  padding: 24 | [24, 32] | [top, right, bottom, left],
  justifyContent: "start" | "center" | "end" | "space_between" | "space_around",
  alignItems: "start" | "center" | "end"
}

Dynamic Sizing

ValueBehavior
"fill_container"Expand to fill parent (requires parent layout)
"fit_content"Shrink to content size
"fill_container(200)"Fill with 200px fallback
"fit_content(200)"Fit content with 200px fallback
200Fixed 200px

Critical Rules:

  • fill_container only works when parent has layout: "vertical" or "horizontal"
  • fit_content only works on frames with layout
  • Cannot have all children as fill_container while parent is fit_content (circular dependency)

batch_design Operations

The primary design tool. Supports Insert, Copy, Update, Replace, Move, Delete, and Generate operations.

Operation Syntax

// Insert - create new node
binding=I(parent, {type: "frame", ...props})

// Copy - duplicate existing node
binding=C("sourceId", parent, {descendants: {...overrides}})

// Update - modify properties (NOT children)
U("nodeId", {property: "value"})
U(binding+"/childId", {content: "Updated"})

// Replace - swap node entirely
binding=R("nodeId", {type: "text", content: "New content"})

// Move - change parent or order
M("nodeId", "newParent", index)

// Delete - remove node
D("nodeId")

// Generate image - apply to frame/rectangle
G(binding, "ai" | "stock", "prompt describing image")

Critical Rules

  1. Maximum 25 operations per call — split larger designs into multiple calls
  2. Bindings are single-use — each Insert/Copy/Replace creates a binding valid only within that call
  3. IDs regenerate on Copy — use descendants in Copy operation, NOT separate Update calls:
// CORRECT - override during copy
copiedBtn=C("btnId", container, {descendants: {"labelId": {content: "New Text"}}})

// WRONG - IDs changed, will fail
copiedBtn=C("btnId", container, {})
U(copiedBtn+"/labelId", {content: "New Text"})  // Error: node not found
  1. Use placeholders for screens — always set placeholder: true while working, remove when done:
// Start work
screen=I(document, {type: "frame", name: "Dashboard", placeholder: true, ...})

// ... do design work ...

// Finish
U("screenId", {placeholder: false})
  1. Never set x/y in flexbox — position properties are ignored when parent has layout

Working with Component Instances

Components are nodes with reusable: true. Instances use type: "ref":

// Insert instance
card=I(container, {type: "ref", ref: "CardComponentId"})

// Override instance properties (root level)
card=I(container, {type: "ref", ref: "CardId", width: "fill_container"})

// Override descendant properties
U(card+"/titleText", {content: "New Title"})
U(card+"/icon", {iconFontName: "settings"})

// Replace slot content entirely
newContent=R(card+"/contentSlot", {type: "frame", layout: "vertical", children: [...]})

// Insert into slots
item=I(card+"/slotId", {type: "ref", ref: "ListItemId"})

Nested Instance Overrides

For deeply nested components, use path notation:

// sidebar contains menuComponent, which contains buttonComponent
U("sidebar/menuComponent/buttonComponent/label", {content: "Updated"})

Guidelines Topics

Call pencil_get_guidelines with specific topics:

TopicWhen to Use
design-systemBuilding with existing components, dashboards, SaaS apps
landing-pageMarketing pages, websites, promotional content
tableData tables, grids
tailwindGenerating Tailwind CSS code from designs
codeGenerating any code from .pen files

Always load relevant guidelines before starting design work.


Style Guides

For creative direction on new designs:

// 1. Get available tags
pencil_get_style_guide_tags()

// 2. Select 5-10 relevant tags
pencil_get_style_guide({
  tags: ["webapp", "dark-mode", "minimal", "professional", "tech"]
})

Style guides provide:

  • Color palettes with hex values
  • Typography scales (fonts, sizes, weights)
  • Spacing systems (gaps, padding)
  • Component patterns with exact properties
  • Design philosophy and dos/don'ts

When to use style guides:

  • Designing from scratch (blank canvas)
  • User requests specific aesthetic
  • Landing pages, marketing sites
  • Exploring visual directions

When to skip:

  • Pure compositional tasks ("add a button here")
  • Using existing design system components

Text Handling

textGrowth Property

Controls text box sizing and wrapping:

ValueWidthHeightLine Wrap
"auto" (default)CalculatedCalculatedNever
"fixed-width"Must specifyCalculatedYes
"fixed-width-height"Must specifyMust specifyYes

Critical: Never set width/height on text without also setting textGrowth.

// Single line, auto-sized
{type: "text", content: "Hello"}

// Multi-line with wrapping
{type: "text", content: "Long paragraph...", textGrowth: "fixed-width", width: "fill_container"}

// Fixed box with overflow
{type: "text", content: "Fixed area", textGrowth: "fixed-width-height", width: 200, height: 100}

Text Alignment

  • textAlign: "left" | "center" | "right" | "justify" — horizontal alignment within text box
  • textAlignVertical: "top" | "middle" | "bottom" — vertical alignment

Note: These only have visible effect with textGrowth set. To position the text box itself, use parent flexbox properties.


Icons

Use icon_font type with these font families:

FamilyStyleExample Names
lucideOutline, roundedhome, settings, user, search, plus, x
featherOutline, roundedSame as lucide
Material Symbols OutlinedOutlinehome, settings, person, search, add, close
Material Symbols RoundedRoundedSame as outlined
Material Symbols SharpSharp cornersSame as outlined
icon=I(container, {
  type: "icon_font",
  iconFontFamily: "lucide",
  iconFontName: "settings",
  width: 24,
  height: 24,
  fill: "#333333"
})

Must specify both width and height for icons.


Images

There is NO "image" node type! Images are fills applied to frame/rectangle nodes.

Using the G (Generate) Operation

// Create frame, then apply image
heroFrame=I(container, {type: "frame", width: 400, height: 300})
G(heroFrame, "ai", "modern office workspace, bright natural lighting")

// Stock photo
G("existingFrameId", "stock", "mountain landscape sunset")

Image Types

TypeSourceBest For
"ai"AI-generatedCustom illustrations, specific scenes, brand assets
"stock"Unsplash photosReal photography, authentic imagery

Writing Effective Prompts

AI prompts — describe scene, style, mood:

  • Weak: "A laptop"
  • Better: "Modern laptop on wooden desk, soft morning light, minimal workspace"

Stock queries — use descriptive keywords:

  • "modern office workspace bright"
  • "team collaboration meeting diverse"
  • "abstract gradient blue purple"

Design Variables

Use pencil_get_variables to read tokens, then reference with $ prefix:

{
  type: "text",
  content: "Hello",
  fill: "$--foreground",           // Color variable
  fontFamily: "$--font-primary"    // Font variable
}

Common variable patterns:

  • $--background, $--foreground — base colors
  • $--primary, $--secondary — brand colors
  • $--font-primary, $--font-secondary — typefaces
  • $--radius-m, $--radius-pill — corner radii
  • $--border — border color

Always use variables over hardcoded values when available.


Visual Verification

Always screenshot after significant changes:

pencil_get_screenshot({filePath: "design.pen", nodeId: "screenId"})

Check for:

  • Correct spacing and alignment
  • Text overflow or clipping
  • Color contrast issues
  • Missing content
  • Layout problems

Use pencil_snapshot_layout to check spatial relationships:

pencil_snapshot_layout({
  filePath: "design.pen",
  parentId: "screenId",
  maxDepth: 2
})

Returns positions, sizes, and layout problems (clipping, overlaps).


Common Patterns

Screen with Sidebar

screen=I(document, {type: "frame", name: "Dashboard", layout: "horizontal", width: 1440, height: "fit_content(900)", fill: "$--background", placeholder: true})
sidebar=I(screen, {type: "ref", ref: "sidebarId", height: "fill_container"})
main=I(screen, {type: "frame", layout: "vertical", width: "fill_container", height: "fill_container", padding: 32, gap: 24})

Card with Header/Content/Actions

card=I(container, {type: "ref", ref: "cardId", width: "fill_container"})
header=R(card+"/headerSlot", {type: "frame", layout: "vertical", gap: 4, padding: 24, width: "fill_container", children: [
  {type: "text", content: "Title", fontSize: 18, fontWeight: "600"},
  {type: "text", content: "Description", fontSize: 14, fill: "$--muted-foreground"}
]})
U(card+"/contentSlot", {layout: "vertical", gap: 16, padding: 24})
U(card+"/actionsSlot", {justifyContent: "end", padding: 24})

Table Structure

Tables follow strict hierarchy: Table → Row → Cell (frame) → Content

tableRow=I("tableId", {type: "frame", layout: "horizontal", width: "fill_container"})
cell1=I(tableRow, {type: "frame", width: "fill_container"})
cellContent1=I(cell1, {type: "text", content: "John Doe"})
cell2=I(tableRow, {type: "frame", width: "fill_container"})
cellContent2=I(cell2, {type: "text", content: "john@example.com"})

Never skip the cell frame — content goes inside cells, not directly in rows.

Form Layout

form=I(card+"/contentSlot", {type: "frame", layout: "vertical", gap: 16, width: "fill_container"})
row=I(form, {type: "frame", layout: "horizontal", gap: 16, width: "fill_container"})
firstName=I(row, {type: "ref", ref: "inputGroupId", width: "fill_container", descendants: {"labelId": {content: "First Name"}}})
lastName=I(row, {type: "ref", ref: "inputGroupId", width: "fill_container", descendants: {"labelId": {content: "Last Name"}}})
email=I(form, {type: "ref", ref: "inputGroupId", width: "fill_container", descendants: {"labelId": {content: "Email"}}})

Metric Cards Grid

metrics=I(content, {type: "frame", layout: "horizontal", gap: 16, width: "fill_container"})
metric1=I(metrics, {type: "ref", ref: "metricCardId", width: "fill_container"})
U(metric1+"/label", {content: "total_users"})
U(metric1+"/value", {content: "12,543"})
U(metric1+"/change", {content: "+12.5%"})

Inspection Tools

pencil_batch_get

Read node structure:

// Read specific nodes
pencil_batch_get({
  filePath: "design.pen",
  nodeIds: ["nodeId1", "nodeId2"],
  readDepth: 3
})

// Search for patterns
pencil_batch_get({
  filePath: "design.pen",
  patterns: [{reusable: true}],  // Find all components
  readDepth: 2,
  searchDepth: 5
})

// Read document root
pencil_batch_get({
  filePath: "design.pen"
})

pencil_get_editor_state

Get current context:

pencil_get_editor_state({include_schema: true})

Returns:

  • Active file path
  • Selected nodes
  • Reusable components list
  • .pen schema (if requested)

Error Handling

Common Errors

ErrorCauseSolution
"Node not found"Wrong ID or ID changed after copyUse descendants in Copy, verify IDs with batch_get
"oldString found multiple times"Ambiguous matchAdd more context to oldString
"Circular dependency"Parent fit_content with all children fill_containerGive at least one child fixed size
"Operations rolled back"Any operation failedFix the failed operation, re-run entire batch

Debugging Tips

  1. Use batch_get before modifying — understand current structure
  2. Screenshot frequently — catch visual issues early
  3. Work in small batches — easier to identify failures
  4. Check layout problems — use snapshot_layout with problemsOnly: true

Anti-Patterns

Don't

  • Use Update on copied node descendants (IDs change)
  • Set x/y when parent has layout
  • Skip cell frames in tables
  • Hardcode colors when variables exist
  • Create image nodes (use G operation on frames)
  • Set width/height on text without textGrowth
  • Forget placeholder: true when working
  • Forget to remove placeholder when done
  • Exceed 25 operations per batch_design call

Do

  • Use descendants in Copy for overrides
  • Use fill_container/fit_content for sizing
  • Follow Table → Row → Cell → Content hierarchy
  • Reference $--variables for colors and fonts
  • Apply images as fills via G operation
  • Set textGrowth before width/height
  • Screenshot after every major change
  • Remove placeholders when sections complete
  • Split large designs into logical batches

Workflow Example: Dashboard Screen

// 1. Get context
pencil_get_editor_state({include_schema: false})
pencil_get_guidelines({topic: "design-system"})
pencil_get_variables({filePath: "app.pen"})

// 2. Inspect available components
pencil_batch_get({
  filePath: "app.pen",
  patterns: [{reusable: true}],
  readDepth: 2
})

// 3. Create screen structure (first batch)
screen=I(document, {type: "frame", name: "Dashboard", layout: "horizontal", width: 1440, height: "fit_content(900)", fill: "$--background", placeholder: true})
sidebar=I(screen, {type: "ref", ref: "sidebarId", height: "fill_container"})
main=I(screen, {type: "frame", layout: "vertical", width: "fill_container", height: "fill_container", padding: 32, gap: 24})

// 4. Add header section (second batch)
header=I("mainId", {type: "frame", layout: "horizontal", justifyContent: "space_between", alignItems: "center", width: "fill_container"})
title=I(header, {type: "text", content: "Dashboard", fontSize: 32, fontWeight: "600"})
actions=I(header, {type: "frame", layout: "horizontal", gap: 12})
btn=I(actions, {type: "ref", ref: "buttonPrimaryId", descendants: {"labelId": {content: "New Item"}}})

// 5. Add metrics row (third batch)
metrics=I("mainId", {type: "frame", layout: "horizontal", gap: 16, width: "fill_container"})
metric1=I(metrics, {type: "ref", ref: "metricCardId", width: "fill_container"})
metric2=I(metrics, {type: "ref", ref: "metricCardId", width: "fill_container"})
metric3=I(metrics, {type: "ref", ref: "metricCardId", width: "fill_container"})

// 6. Verify
pencil_get_screenshot({filePath: "app.pen", nodeId: "screenId"})

// 7. Remove placeholder when done
U("screenId", {placeholder: false})

Tool Reference Summary

ToolPurposeKey Parameters
pencil_get_editor_stateGet current file, selection, schemainclude_schema
pencil_get_guidelinesLoad design rulestopic
pencil_get_style_guide_tagsList available style tags
pencil_get_style_guideGet visual directiontags[] or id
pencil_get_variablesRead design tokensfilePath
pencil_batch_getRead node structurenodeIds, patterns, readDepth, searchDepth
pencil_batch_designCreate/modify designfilePath, operations
pencil_get_screenshotVisual verificationfilePath, nodeId
pencil_snapshot_layoutCheck spatial layoutfilePath, parentId, maxDepth, problemsOnly
pencil_find_empty_space_around_nodeFind placement spacenodeId, direction, width, height, padding
pencil_set_variablesUpdate design tokensfilePath, variables
pencil_open_documentOpen/create .pen filefilePathOrTemplate

This skill covers the Pencil MCP design system. For authoritative documentation, consult pencil.dev or use pencil_get_guidelines with specific topics.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

pencil

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

clawhub-install

Download and install skills from ClawHub directly via curl, bypassing official CLI rate limits. Use when the user wants to install one or more ClawHub skills...

Registry SourceRecently Updated
0199
upupc
Coding

Homebrew Bridge

Expose Mac Homebrew tools like brew, gh, and other /opt/homebrew/bin CLIs on a Linux OpenClaw gateway by installing explicit same-LAN SSH wrappers with optio...

Registry SourceRecently Updated