Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
isala404 avatar

Excalidraw Render

  • 10 installs
  • Updated July 30, 2026
  • isala404/dotfiles

Generate polished Excalidraw architecture and flow diagrams from rough input by writing .excalidraw JSON and rendering it to PNG locally.

About

Produces clean Excalidraw diagrams from ASCII, descriptions, or sketches, rendering to PNG and iterating on layout via vision. A developer uses it when they need an architecture, data-flow, or sequence diagram without hand-editing JSON.

  • Main agent critiques rendered PNGs while a Haiku sub-agent authors the JSON to save tokens
  • Hard cap of three render-critique cycles

Excalidraw Render by the numbers

  • 10 all-time installs (skills.sh)
  • +2 installs in the week ending Aug 2, 2026 (Skillselion tracking)
  • Ranked #1,450 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
  • Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/isala404/dotfiles --skill excalidraw-render

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs10
Last updatedJuly 30, 2026
Repositoryisala404/dotfiles

What it does

Generate polished Excalidraw architecture and flow diagrams from rough input by writing .excalidraw JSON and rendering it to PNG locally.

Files

SKILL.mdMarkdownGitHub ↗

Excalidraw Render Skill

Make a polished Excalidraw diagram from a rough input, without baby-sitting. The main agent thinks in pixels (by viewing rendered PNGs); a Haiku sub-agent does the JSON work.

---

Core principle — cost discipline

A .excalidraw file is huge: a 10-box diagram is 3–5K tokens. Reading or editing it with the main model is wasteful and slow. Vision on a rendered PNG is far cheaper and far more accurate for layout judgment.

The main agent MUST:

  • ✅ View the rendered PNG to judge correctness (this is what vision is for)
  • ✅ Plan layouts up-front (positions, sizes, colors) — architectural judgment
  • ✅ Critique specific visual issues from the PNG (overlaps, clipping, misalignment)
  • ✅ Dispatch surgical fix instructions to a Haiku sub-agent

The main agent MUST NOT:

  • ❌ Read the .excalidraw JSON file — delegate to Haiku
  • ❌ Write the .excalidraw JSON inline — delegate to Haiku
  • ❌ Edit JSON with str_replace manually — delegate to Haiku
  • ❌ Loop forever — hard cap at 3 render–critique cycles

---

Workflow

User input (ASCII / description / sketch)
        │
[MAIN]  Plan layout                ← architectural judgment
        │
[HAIKU] Write diagram.excalidraw   ← author the JSON
        │
[BASH]  npx excalidraw-cli convert  ← produce PNG
        │
[MAIN]  View the PNG               ← visual judgment
        │
   Issues? ── yes ──► [HAIKU] Apply specific fixes ──┐
        │                                             │
       no                                             │
        │                                             │
   Present to user      ◄────────────────────────────┘
                                (max 3 cycles)

---

Step 1 — Plan the layout (main agent)

Before any tool calls, decide:

1. Components — what shapes (rectangles, diamonds, ellipses) and what each says 2. Layout family — vertical flow, horizontal pipeline, hub-and-spoke, or 3-column data flow 3. Coordinates — give every shape an exact (x, y, width, height) using the Sizing Rules 4. Colors — pick from the palette by role 5. Connections — list arrows as (from_id → to_id, label, style) 6. Zones / titles / annotations — optional backgrounds, headers, side labels

Write this as a plain-text layout brief. NOT JSON. Haiku will produce the JSON.

Preserve all detail from the user's input. If the ASCII says "Fail = Stop & Notify", that label appears verbatim. Don't summarize. Resize boxes to fit the full text.

---

Step 2 — Hand off to Haiku to author the JSON

Spawn a claude-haiku-4-5 sub-agent. Pass it the layout brief, the output path, and inline the Schema Cheat Sheet below so it doesn't have to guess.

Sub-agent prompt template (author):

You are authoring a valid Excalidraw scene file. Output the complete JSON to:
  <ABS_PATH>/diagram.excalidraw

# Layout brief
<paste the brief from Step 1 — every shape with exact x/y/w/h, every arrow, every label>

# Schema cheat sheet
<paste the "Schema cheat sheet" section from this skill verbatim>

# Rules
- Use only the colors from the palette in the brief.
- Every shape has an `id` so arrows can reference it.
- Labels inside shapes are SEPARATE `text` elements with `containerId` set to the shape's id, AND the shape's `boundElements` includes `{ "type": "text", "id": "<label-id>" }`.
- Arrow labels are SEPARATE `text` elements with `containerId` set to the arrow's id, plus matching `boundElements` on the arrow.
- Set `roughness: 0` everywhere for clean lines.
- Output ONLY the file. No commentary, no fences. Make sure it parses with `JSON.parse`.

The main agent does NOT review the resulting JSON. Trust Haiku and move on to rendering.

---

Step 3 — Render

npx @swiftlysingh/excalidraw-cli convert <path>/diagram.excalidraw --format png -o <path>/diagram.png --scale 2

No setup needed — npx handles installation on first run. Uses @excalidraw/utils under the hood (via jsdom + resvg-js), no headless browser required. If rendering fails for a large file, spawn a Haiku to summarize what's in the file (Haiku reads it, main agent never does) before fixing.

---

Step 4 — View the PNG and critique

view the PNG. Walk this checklist and write down concrete fixes in pixel / id terms:

CheckWhat to look forFix instruction to Haiku
OverlapTwo shapes touch or cross"Move shape <id> to (x, y) = (...)."
Clipped arrow labelLabel cut into a shape or smudged"Increase gap between <a> and <b> to 180px (set <b>.x to ...)."
Text overflowLabel spills outside the box"Set <id>.width = 240 (or shorten label to '...')."
Cramped zoneZone background hugs children"Resize zone <id> to (x,y,w,h) = (...)."
Off-balanceContent piled to one side"Shift columns: rebalance to center on x=400."
Missing title / layer labelsNo header; layers unlabeled"Add text at (x,y), content '...', fontSize 24."
Wrong arrow targetArrow points to wrong box"Change arrow <id>'s endBinding.elementId from X to Y."
Wrong color for roleDB shown blue, API green, etc."Recolor <id> to bg=#b2f2bb stroke=#2f9e44 (Database role)."

Main agent describes WHAT needs to change in pixel/id terms; Haiku reads the file and applies it. Don't try to compute every coordinate yourself for more than 2-3 elements — let Haiku do bulk edits.

---

Step 5 — Hand off fixes to Haiku

Sub-agent prompt template (fix):

Edit the existing Excalidraw scene at <ABS_PATH>/diagram.excalidraw.

Apply ONLY these changes:
1. <fix #1>
2. <fix #2>
3. <fix #3>

# Schema cheat sheet
<paste the "Schema cheat sheet" section so Haiku knows the structure>

# Rules
- Read the file. Modify only the elements named. Preserve every other field exactly.
- When you move a shape, ALSO move its label text element (same containerId) by the same delta so they stay aligned.
- When you resize a shape, recompute the label element's x/y/width to stay centered.
- Write back to the same path. The result MUST parse with `JSON.parse`.
- Output ONLY the path of the file you wrote. No commentary.

Re-render and re-view. Hard cap: 3 cycles total. After cycle 3, present what you have and list any remaining issues explicitly. Don't loop more — the user can take it from there.

---

Step 6 — Present

Show the final rendered PNG with present_files. Briefly summarize: components shown, connections, and any unresolved issues. Both diagram.excalidraw (editable) and diagram.png (image) should be available.

---

Schema cheat sheet (copy-paste this into Haiku prompts)

Scene wrapper:

{
  "type": "excalidraw",
  "version": 2,
  "source": "https://excalidraw.com",
  "elements": [ /* see below */ ],
  "appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },
  "files": {}
}

Rectangle / ellipse / diamond:

{
  "id": "react-app",
  "type": "rectangle",
  "x": 40, "y": 50, "width": 220, "height": 90,
  "angle": 0,
  "strokeColor": "#1971c2",
  "backgroundColor": "#a5d8ff",
  "fillStyle": "solid",
  "strokeWidth": 2,
  "strokeStyle": "solid",
  "roughness": 0,
  "opacity": 100,
  "groupIds": [], "frameId": null, "roundness": { "type": 3 },
  "seed": 1, "version": 1, "versionNonce": 1, "isDeleted": false,
  "boundElements": [{ "type": "text", "id": "react-app-label" }],
  "updated": 1, "link": null, "locked": false
}

Text label inside a shape (separate element, bound by containerId):

{
  "id": "react-app-label",
  "type": "text",
  "x": 50, "y": 75,
  "width": 200, "height": 40,
  "angle": 0,
  "strokeColor": "#1e1e1e", "backgroundColor": "transparent",
  "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid",
  "roughness": 0, "opacity": 100,
  "groupIds": [], "frameId": null, "roundness": null,
  "seed": 2, "version": 1, "versionNonce": 2, "isDeleted": false,
  "boundElements": null, "updated": 1, "link": null, "locked": false,
  "fontSize": 18, "fontFamily": 1,
  "text": "React App\nFrontend",
  "textAlign": "center", "verticalAlign": "middle",
  "containerId": "react-app",
  "originalText": "React App\nFrontend",
  "lineHeight": 1.25
}

Arrow with bindings and a label:

{
  "id": "arrow1",
  "type": "arrow",
  "x": 260, "y": 95,
  "width": 180, "height": 0,
  "angle": 0,
  "strokeColor": "#1971c2", "backgroundColor": "transparent",
  "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid",
  "roughness": 0, "opacity": 100,
  "groupIds": [], "frameId": null, "roundness": { "type": 2 },
  "seed": 3, "version": 1, "versionNonce": 3, "isDeleted": false,
  "boundElements": [{ "type": "text", "id": "arrow1-label" }],
  "updated": 1, "link": null, "locked": false,
  "points": [[0, 0], [180, 0]],
  "lastCommittedPoint": null,
  "startBinding": { "elementId": "react-app", "focus": 0, "gap": 4 },
  "endBinding":   { "elementId": "api-server", "focus": 0, "gap": 4 },
  "startArrowhead": null, "endArrowhead": "arrow",
  "elbowed": false
}

strokeStyle: "solid" (normal) · "dashed" (async / optional) · "dotted" (weak dependency).

---

Color palette

RoleBackgroundStroke
Frontend / UI#a5d8ff#1971c2
Backend / API#d0bfff#7048e8
Database#b2f2bb#2f9e44
Storage#ffec99#f08c00
AI / ML#e599f7#9c36b5
External API#ffc9c9#e03131
Queue / Event#fff3bf#fab005
Cache#ffe8cc#fd7e14
Decision / Gate#ffd8a8#e8590c
Zone / Group#e9ecef#868e96

Same role → same color. Limit a diagram to 3–4 fill colors.

---

Sizing rules

The #1 cause of ugly diagrams is cramping. When unsure, double the gap.

PropertyValue
Box width200–240px
Box height80–120px (3–4 lines comfortable)
Horizontal gap, labeled arrow150–200px
Horizontal gap, unlabeled arrow100–120px
Column pitch, labeled400px
Column pitch, unlabeled340px
Row pitch250–300px
Font, body16–18px
Font, title22–28px
Font, annotations13–14px
Zone padding around children50–60px on every side
Zone opacity25–40

Labeled-arrow visibility test: if the label is more than half the gap between its two boxes, increase the gap. Common offenders: "auto deploy", "rollback on failure", "All pass" — these are 100–150px wide and clip below ~150px gap.

Zone sizing rule: x = min(child_x) − 50, y = min(child_y) − 55, w = max(child_right) − x + 60, h = max(child_bottom) − y + 60.

---

Layout patterns

  • Vertical flow (default): top→bottom, multi-column. Use for layered architectures and request flows.
  • Horizontal pipeline: left→right, single row. Use for ETL, transforms.
  • Hub-and-spoke: central shape at center, others radial. Use for event buses, brokers.
  • 3-column data flow: left = layer names (gray, x<0), center = flow boxes (x: 60–360), right = data-form annotations (orange, x: 570+). Use for parameter threading and call-chain traces.

---

Common mistakes

MistakeFix
Main agent read or edited the .excalidraw JSONStop. Delegate to Haiku. The skill exists to avoid this.
Arrow labels clippedIncrease gap between the two boxes to ≥150px
Shapes overlapMove one box by ≥200px; re-check zone bounds
Zone hugs its childrenRecompute zone box with the Zone sizing rule above
Label drifted away from its shape after movingWhen Haiku moves a shape, it must move the bound text element by the same delta
Dropped detail from user's sampleThe sample is the source of truth. Every label verbatim. Resize boxes.
Same issue persists 3 cycles in a rowStop. Present current PNG. List as open issue. Don't loop.
Same fix applied twice with no effectTry a different approach — wider gap, restructure — not the same fix harder.
Mixed colors for the same roleOne role → one color. Enforce on every iteration.

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.