excalidraw
Excalidraw Diagramming Skill
Turn text descriptions into professional diagrams on a live Excalidraw canvas. Your agent draws, screenshots its own work, and iterates until it looks right.
Prerequisites: MCP Setup
Before using this skill, the Excalidraw MCP server must be installed and running.
One-time install
git clone https://github.com/yctimlin/mcp_excalidraw && cd mcp_excalidraw
npm ci && npm run build
Add to Claude Code
claude mcp add excalidraw -s user \
-e EXPRESS_SERVER_URL=http://localhost:3000 \
-- node /absolute/path/to/mcp_excalidraw/dist/index.js
Replace /absolute/path/to/ with wherever you cloned the repo.
Start the canvas (every session)
cd /path/to/mcp_excalidraw
PORT=3000 npm run canvas
Then open http://localhost:3000 in your browser to see the live canvas.
Verify it works
Once Claude Code starts, you should see excalidraw/* tools in your tool list. Try: "Draw a simple flowchart with three boxes."
How It Works
You describe a diagram
-> Agent plans the layout (coordinates, spacing, colors)
-> batch_create_elements (draws everything in one call)
-> get_canvas_screenshot (agent sees what it drew)
-> Fixes any issues (overlap, truncation, bad arrows)
-> Repeats until clean
-> Exports (PNG, SVG, .excalidraw, or shareable URL)
The key insight: the agent can see its own canvas via screenshots. This creates a self-correcting feedback loop that produces clean diagrams without manual tweaking.
Workflow (ALWAYS follow this order)
- Read the design guide -- call
read_diagram_guidefirst for color palette and sizing rules - Plan the layout -- decide on structure based on content (vertical cards, horizontal flow, grid)
- Create 3 variations -- ALWAYS produce 3 different layout options before committing. Save each as a snapshot. Show a screenshot of each.
- Let the user choose -- present all 3 with a short description of each
- Refine the chosen version -- clean up based on feedback
- Final screenshot -- verify everything looks right before exporting
Variation Strategies
When creating 3 options, vary these dimensions:
- Layout direction -- vertical stack vs horizontal flow vs grid
- Shape variety -- all rectangles vs mixed (circles, diamonds, rectangles)
- Information density -- minimal (just labels) vs detailed (labels + descriptions + examples)
- Visual personality -- clean/professional vs playful/sketchy (roughness: 0 vs 1)
Design Principles
#1 Rule: Transparent Backgrounds (CRITICAL)
Always use "backgroundColor": "transparent" on shapes. This is the single biggest design improvement. Filled rectangles look flat and amateurish. Transparent boxes with colored strokes look professional, especially on dark canvases where the background shows through.
The only exceptions:
- Badge circles (numbered steps) -- these get solid fills so the number is readable
- Glow effect layers -- these use low-opacity fills behind the main shape
- Scatter plot dots -- small data points that need to be visible
Everything else: transparent background, colored stroke only.
{"type": "rectangle", "x": 100, "y": 100, "width": 200, "height": 60,
"backgroundColor": "transparent", "strokeColor": "#3b82f6", "roughness": 0,
"text": "My Service"}
Dark Canvas First
Design for dark mode. Use bright stroke colors (#3b82f6, #22c55e, #a78bfa) against the dark canvas. Light gray text (#cbd5e1, #e2e8f0) for body copy. Muted gray (#64748b, #94a3b8) for secondary text and subtitles. Gold (#fbbf24) for footer taglines.
Language
- Always use plain language -- no technical jargon unless explicitly asked
- Write labels as if explaining to someone who's never coded
- Descriptions should be conversational, not clinical
Color Palette
Each color has a stroke (for borders/outlines) and a fill (only for badges/dots). Use stroke colors on shapes with transparent backgrounds.
| Color | Stroke | Fill (badges only) |
|---|---|---|
| Blue | #3b82f6 |
#3b82f6 |
| Purple | #8b5cf6 |
#8b5cf6 |
| Green | #22c55e |
#22c55e |
| Orange/Amber | #f59e0b |
#f59e0b |
| Red | #ef4444 |
#ef4444 |
| Cyan | #06b6d4 |
#06b6d4 |
| Pink | #ec4899 |
#ec4899 |
| Lime | #a3e635 |
#a3e635 |
| Gray (structure) | #475569 |
-- |
| Gray (subtle) | #334155 |
-- |
Text colors:
| Role | Hex |
|---|---|
| Title / heading | #e2e8f0 |
| Body text | #cbd5e1 |
| Subtitle / secondary | #64748b or #94a3b8 |
| Footer tagline | #fbbf24 |
| Code / monospace | #22c55e (prompt) or #94a3b8 (body) |
Colored Dot Bullets
Instead of text bullet characters (•), use small filled ellipses next to free-standing text. This looks dramatically better:
{"type": "ellipse", "x": 105, "y": 345, "width": 12, "height": 12,
"backgroundColor": "#3b82f6", "strokeColor": "#3b82f6", "roughness": 0},
{"type": "text", "x": 128, "y": 340, "width": 300, "height": 22,
"text": "Think through problems", "fontSize": 16, "fontFamily": "excalifont", "strokeColor": "#cbd5e1"}
Use a different color for each bullet to add visual variety. Keep dots at 10-12px diameter.
Visual Elements
- Emojis as icons -- use relevant emojis in labels (e.g., "🧠 Think", "⚡ Act")
- Numbered badge circles -- solid-filled circles with numbers for step sequences
- Use
roughness: 0for clean badge circles - Badge size: 40x40px, font size 20
- Use
- Punchy footer tagline -- include a one-liner at the bottom in gold (
#fbbf24) that captures the "so what" - Section divider lines -- thin dashed lines (
strokeColor: "#334155",strokeStyle: "dashed") between sections - Separator lines inside cards -- thin solid lines below the title area to separate header from content
Font Rules
- Helvetica (
"helvetica") -- titles, headings, and labels (clean and professional) - Excalifont (
"excalifont") -- descriptions, bullets, secondary text (friendly hand-drawn feel) - Monospace (
3) -- code snippets, terminal prompts, file names - Do NOT use Lilita One (too cartoony) or Comic Shanns
| Element | Font | Size |
|---|---|---|
| Diagram title | Helvetica | 24-44px |
| Section heading | Helvetica | 20-28px |
| Element label | Helvetica | 14-16px |
| Description / bullets | Excalifont | 13-16px |
| Subtitle | Excalifont | 13-15px |
| Footer tagline | Excalifont | 12-13px |
| Code text | Monospace | 11-14px |
Text Handling (CRITICAL -- prevent overflow)
- Always pre-wrap text with manual
\nline breaks -- never rely on auto-wrap - Max ~40 characters per line for description text at 14px font
- Max ~35 characters per line for heading text at 22px font
- 20px+ padding between text and box edges on all sides
- For simple label-only boxes, size width to
max(100, labelCharCount * 9)
Building Section by Section
For complex diagrams, build one section at a time using batch_create_elements. This prevents overwhelming the canvas and makes debugging easier. Take a screenshot after each section to verify before moving on.
Multi-Diagram Layouts
When placing multiple diagrams on one canvas, use a grid layout:
- 2 diagrams: side by side, ~530px apart
- 3 diagrams: row of 3, ~530px column spacing
- 6 diagrams (3x2): columns at x=50, x=560, x=1080. Rows at y=20, y=460. Each cell ~480px wide, ~420px tall.
Namespace all element IDs by diagram number (e.g., d1-title, d2-hub, d3-s1) to avoid collisions.
10 Visual Techniques
These are the building blocks for every diagram. Combine them to create professional visuals.
1. Layered Glow Effect
Stack 2-3 rectangles at decreasing opacity behind a shape to create depth.
{"id": "glow-outer", "type": "rectangle", "x": 95, "y": 95, "width": 210, "height": 70,
"backgroundColor": "#a5d8ff", "opacity": 20, "strokeColor": "transparent"},
{"id": "glow-inner", "type": "rectangle", "x": 98, "y": 98, "width": 204, "height": 64,
"backgroundColor": "#a5d8ff", "opacity": 40, "strokeColor": "transparent"},
{"id": "main-box", "type": "rectangle", "x": 100, "y": 100, "width": 200, "height": 60,
"text": "Core Service", "backgroundColor": "#a5d8ff", "strokeColor": "#1971c2"}
2. Color-Coded Zones
Low-opacity background rectangles group related elements. Use a free-standing text label at the top corner (never use text on the zone rectangle itself -- it centers and overlaps children).
{"id": "zone-bg", "type": "rectangle", "x": 50, "y": 50, "width": 500, "height": 300,
"backgroundColor": "#e9ecef", "opacity": 30, "strokeColor": "#868e96"},
{"id": "zone-label", "type": "text", "x": 70, "y": 60, "width": 200, "height": 30,
"text": "Backend Services", "fontSize": 18, "fontFamily": "helvetica"}
3. Bound Arrows with Labels
Arrows snap to shapes using element IDs. Labels describe the relationship.
{"id": "svc-a", "type": "rectangle", "x": 100, "y": 100, "width": 160, "height": 60,
"backgroundColor": "transparent", "strokeColor": "#3b82f6", "roughness": 0, "text": "API Gateway"},
{"id": "svc-b", "type": "rectangle", "x": 400, "y": 100, "width": 160, "height": 60,
"backgroundColor": "transparent", "strokeColor": "#22c55e", "roughness": 0, "text": "Database"},
{"type": "arrow", "x": 0, "y": 0, "startElementId": "svc-a", "endElementId": "svc-b", "text": "SQL"}
4. Line Styles as Meaning
- Solid = synchronous / primary flow
- Dashed (
strokeStyle: "dashed") = asynchronous / secondary - Dotted (
strokeStyle: "dotted") = optional / planned
5. Diamond Decision Nodes
Classic flowchart branching with Yes/No paths.
{"id": "decision", "type": "diamond", "x": 300, "y": 200, "width": 140, "height": 100,
"backgroundColor": "transparent", "strokeColor": "#f59e0b", "roughness": 0, "text": "Auth OK?"},
{"id": "yes-path", "type": "rectangle", "x": 150, "y": 380, "width": 140, "height": 60,
"backgroundColor": "transparent", "strokeColor": "#22c55e", "roughness": 0, "text": "Proceed"},
{"id": "no-path", "type": "rectangle", "x": 450, "y": 380, "width": 140, "height": 60,
"backgroundColor": "transparent", "strokeColor": "#ef4444", "roughness": 0, "text": "Reject"},
{"type": "arrow", "x": 0, "y": 0, "startElementId": "decision", "endElementId": "yes-path",
"strokeColor": "#22c55e", "text": "Yes"},
{"type": "arrow", "x": 0, "y": 0, "startElementId": "decision", "endElementId": "no-path",
"strokeColor": "#ef4444", "text": "No"}
6. Mixed Shape Types
Use shape type to encode meaning:
- Ellipse = actors, users, external systems
- Rectangle = services, processes, components
- Diamond = decisions, conditions
7. Numbered Badge Circles
Solid-filled circles with white numbers for step sequences:
{"id": "badge-1", "type": "ellipse", "x": 100, "y": 100, "width": 50, "height": 50,
"backgroundColor": "#1971c2", "strokeColor": "#1971c2", "roughness": 0,
"text": "1", "fontSize": 24, "textAlign": "center"}
8. Emoji Icons in Labels
Emojis render beautifully at any size. Use them to make labels scannable:
{"type": "rectangle", "x": 100, "y": 100, "width": 200, "height": 60,
"backgroundColor": "transparent", "strokeColor": "#3b82f6", "roughness": 0,
"text": "🧠 Claude thinks"}
9. Mermaid Conversion
Convert existing Mermaid diagrams to editable Excalidraw elements:
create_from_mermaid("graph TD\n A[Start] --> B{Decision}\n B -->|Yes| C[Do Thing]\n B -->|No| D[Skip]")
After conversion, use set_viewport with scrollToContent: true to auto-fit the view.
10. Screenshot-Iterate Loop
The most important technique. After every batch of elements:
batch_create_elements -> get_canvas_screenshot -> evaluate -> fix -> re-screenshot
Never skip the screenshot step. Always verify before moving on.
Layout Rules
These prevent the most common diagram problems.
Spacing
- Between shapes: 30-40px gap between connected cards/zones
- Vertical tiers: 80-120px between rows (enough room for arrow labels)
- Shape width:
max(600, labelCharCount * 9)for boxes with heading + description;max(160, labelCharCount * 9)for simple label boxes - Shape height: 60px single-line, 80px two-line, 110px for step cards with descriptions
- Zone padding: 50px on all sides around contained elements
Alignment
- All same-role elements should share the same x or y coordinate
- Center titles and footers relative to the card stack width
- Badges aligned at the same x offset within their cards
Arrows
- Always use
startElementId/endElementIdto bind arrows to shapes (auto-routes to edges) - Keep arrow labels under 12 characters
- If an arrow crosses through an unrelated shape, add a waypoint to route around it
Curved arrows (smooth arc over obstacles):
{"type": "arrow", "x": 100, "y": 100, "points": [[0, 0], [50, -40], [200, 0]], "roundness": {"type": 2}}
Elbowed arrows (right-angle routing):
{"type": "arrow", "x": 100, "y": 100, "points": [[0, 0], [0, -50], [200, -50], [200, 0]], "elbowed": true}
Zone Labels (Critical)
Never put a text label on a large background rectangle. Excalidraw centers it in the middle of the zone, overlapping everything inside.
Instead, create a separate text element positioned at the top corner of the zone.
Custom Element IDs
Always assign custom id values (e.g., "id": "auth-svc") so arrows can reference them and you can update elements later by name.
Sizing Defaults
| Element | Width | Height |
|---|---|---|
| Step card (vertical) | 340-460px | 40-55px |
| Step card (horizontal) | 110-220px | 38-55px |
| Badge circle | 40px | 40px |
| Bullet dot | 10-12px | 10-12px |
| Hub node (ellipse) | 140px | 65px |
| Satellite node | 100-130px | 38-45px |
| Code block | 240-700px | 50px |
| Layer row | 460-600px | 45px |
| Inner service box | 90-100px | 28-32px |
Quality Checklist
Run this after every batch_create_elements. Take a screenshot and check:
- Text truncation -- All label text fully visible? If not, increase shape width/height
- Text overflow -- Text staying within box edges with 20px+ padding on all sides?
- Overlap -- Any shapes sharing space? Background zones must contain children with padding
- Arrow crossing -- Do arrows cut through unrelated shapes? Route around with waypoints
- Arrow-label overlap -- Labels at midpoint overlapping shapes? Shorten label or adjust path
- Spacing -- At least 30px gap between elements
- Readability -- Font size 14+ for body, 22+ for headings, 36 for title
- Zone labels -- Not centered in background zones (use free-standing text instead)
- Alignment -- Same-role elements sharing coordinates?
- Font consistency -- Helvetica for headings, Excalifont for descriptions?
If anything fails: stop, fix it, re-screenshot, then continue.
Anti-Patterns (DO NOT do these)
- Filled backgrounds on shapes -- use
"backgroundColor": "transparent"always (except badges/dots) - Text bullet characters (
•,-) -- use colored ellipse dots instead - Single-line text that overflows the box edge
- Technical jargon without being asked for it
- Skipping the 3-variation step
- Forgetting to take a screenshot after building
- Using only one font family throughout
- Tiny fonts below 14px
- Boxes narrower than 600px when they contain multi-line text
- Forgetting to save snapshots before moving to the next variation
- Building the entire diagram in one
batch_create_elementscall -- build section by section - Using the same stroke color for every element -- vary colors to encode meaning
What Works Well
- Rectangles, ellipses, diamonds -- all shape types
- Bound arrows with auto-routing
- Arrow labels
- Line styles (solid, dashed, dotted)
- Full color palette with fills and strokes
- Opacity for glow/shadow/zone effects
- Font sizes from 14px to 36px+
- Emoji icons (render great at any size, inside or outside shapes)
- Mermaid-to-Excalidraw conversion
- Export to .excalidraw, PNG, SVG, and shareable URL
- Canvas screenshots for self-verification
Known Limitations
- Images -- MCP tool doesn't expose file path or base64 params. Workaround: drag images into the browser at localhost:3000
- Freedraw -- Needs point arrays the MCP tool can't accept. Workaround: draw freehand in browser
- SVG import --
import_sceneonly accepts .excalidraw JSON, not raw SVGs. Workaround: paste SVGs in the browser (native Excalidraw feature)
Workflow: New Diagram
clear_canvasto start fresh- Call
read_diagram_guidefor the built-in design reference - Plan your coordinate grid (sketch tiers and x-positions)
- Create 3 variations -- save each as a snapshot, screenshot each
- Present all 3, let user choose
- Refine the chosen version
get_canvas_screenshot-- run the Quality Checklist- Fix issues with
update_element, re-screenshot - Export when clean:
export_to_image(PNG/SVG) orexport_scene(.excalidraw)
Workflow: Refine Existing Diagram
describe_sceneto understand what's on the canvas (IDs, positions, labels)- Identify elements by ID or label text (not coordinates)
update_elementto resize, recolor, or moveget_canvas_screenshotto verify- If an element won't update, try
unlock_elementsfirst
Workflow: Snapshots (Undo Safety)
snapshot_scenewith a name before risky changes- Make changes, screenshot to evaluate
restore_snapshotto roll back if needed
Workflow: Export
.excalidrawJSON (re-editable):export_scene- PNG image:
export_to_imagewithformat: "png" - SVG image:
export_to_imagewithformat: "svg" - Shareable URL:
export_to_excalidraw_url(uploads to excalidraw.com)
Error Recovery
- Elements not appearing? They may be off-screen. Use
set_viewportwithscrollToContent: true - Arrow not connecting? Verify element IDs exist with
get_element - Canvas in a bad state?
snapshot_scenefirst, thenclear_canvasand rebuild - Element won't update? May be locked -- call
unlock_elementsfirst - Duplicate text elements appearing? The frontend auto-syncs and can re-inject bound texts. Fix: use
query_elementsto find text elements with acontainerId, delete the extras