Use when user requests diagrams, flowcharts, architecture charts, or visualizations. Also use proactively when explaining systems with 3+ components, complex data flows, or relationships that benefit from visual representation. Generates .excalidraw files and exports to PNG/SVG via Kroki API or locally using excalidraw-brute-export-cli.
Add this skill
npx mdskills install Agents365-ai/excalidraw-skillComprehensive diagram generation with design system, export automation, and proactive triggers
1---2name: excalidraw3description: Use when user requests diagrams, flowcharts, architecture charts, or visualizations. Also use proactively when explaining systems with 3+ components, complex data flows, or relationships that benefit from visual representation. Generates .excalidraw files and exports to PNG/SVG via Kroki API or locally using excalidraw-brute-export-cli.4homepage: https://github.com/Agents365-ai/excalidraw-skill5metadata: {"openclaw":{"requires":{"bins":["curl"]},"emoji":"🎨"}}6---78# Excalidraw Diagrams910## Overview1112Generate `.excalidraw` JSON files and export to PNG/SVG.1314**Two export options:**15- **Kroki API** (`curl`) — zero install, SVG output only16- **excalidraw-brute-export-cli** — local Firefox-based, PNG + SVG1718**Supported formats:** PNG (local CLI only), SVG (both options). PDF is NOT supported.1920## When to Use2122**Explicit triggers:** user says "画图", "diagram", "visualize", "flowchart", "draw", "架构图", "流程图"2324**Proactive triggers:**25- Explaining a system with 3+ interacting components26- Describing a multi-step process or decision tree27- Comparing architectures or approaches side by side2829**Skip when:** a simple list or table suffices, or user is in a quick Q&A flow3031## Prerequisites3233### Option A: Kroki API (recommended — zero install, SVG only)3435```bash36# Just needs curl (pre-installed on macOS/Linux/Windows Git Bash)37curl --version38```3940No additional setup. SVG rendered via `https://kroki.io`.4142### Option B: Local CLI (required for PNG)4344The CLI uses **Firefox** (not Chromium). Check and install:4546```bash47npm install -g excalidraw-brute-export-cli48npx playwright install firefox49```5051**macOS patch (one-time, required):**52```bash53CLI_MAIN=$(npm root -g)/excalidraw-brute-export-cli/src/main.js54sed -i '' 's/keyboard.press("Control+O")/keyboard.press("Meta+O")/' "$CLI_MAIN"55sed -i '' 's/keyboard.press("Control+Shift+E")/keyboard.press("Meta+Shift+E")/' "$CLI_MAIN"56```5758**Windows/Linux:** No patch needed.5960## Workflow61620. **Update check (notify, don't pull)** — first use per conversation. Throttle to once per 24 h via `<this-skill-dir>/.last_update`; never mutate the skill directory without explicit user consent.6364 - If `.last_update` exists and is <24 h old, skip this step entirely.65 - Otherwise, fetch the latest tag from upstream:66 ```bash67 git -C <this-skill-dir> ls-remote --tags origin 'v*' 2>/dev/null \68 | awk '{print $2}' | sed 's|refs/tags/||' | sort -V | tail -169 ```70 - Compare with this skill's `metadata.version` from the frontmatter. If the upstream tag is strictly newer (semver), tell the user one line and ask:71 > "A newer version of this skill is available: vX.Y.Z → vA.B.C. Want me to `git pull`?"7273 If they say yes, run `git -C <this-skill-dir> pull --ff-only`. Refresh `.last_update` either way so the prompt doesn't repeat for 24 hours.74 - If upstream is the same or older, refresh `.last_update` silently and continue.75 - On any failure (offline, not a git checkout — e.g. ClawHub-installed copy, read-only path, no permission), swallow the error silently and continue with the user's task. Do not mention the failure.761. **Check deps** — use Kroki (curl) for SVG; use local CLI for PNG772. **Plan** — identify diagram type, pick a visual pattern, choose color palette783. **Generate** — write `.excalidraw` JSON file (section-by-section for large diagrams)794. **Export** — run Kroki or CLI command805. **Report** — tell user the output file path8182## Design Principles8384### Default style8586- `roughness: 0` — clean, modern look for all technical diagrams (use `1` only when user requests hand-drawn/casual style)87- `fontFamily: 2` (Helvetica) — professional look; use `1` (Virgil) only for casual/sketch style, `3` (Cascadia) for code snippets88- `fillStyle: "solid"` — default fill8990### Font size hierarchy9192| Level | Size | Use for |93|-------|------|---------|94| Title | 28px | Diagram title |95| Header | 24px | Section/group headers |96| Label | 20px | Primary element labels |97| Description | 16px | Secondary text, descriptions |98| Note | 14px | Annotations, fine print |99100### Color palette101102Follow the **60-30-10 rule**: 60% whitespace/neutral, 30% primary accent, 10% highlight.103104**Semantic fill colors** (use with `strokeColor` one shade darker):105106| Category | Fill | Stroke | Use for |107|----------|------|--------|---------|108| Primary / Input | `#dbeafe` | `#1e40af` | Entry points, APIs, user-facing |109| Success / Data | `#dcfce7` | `#166534` | Data stores, success states |110| Warning / Decision | `#fef9c3` | `#854d0e` | Decision points, conditions |111| Error / Critical | `#fee2e2` | `#991b1b` | Errors, alerts, critical paths |112| External / Storage | `#f3e8ff` | `#6b21a8` | External services, databases, AI/ML |113| Process / Default | `#e0f2fe` | `#0369a1` | Standard process steps |114| Trigger / Start | `#fed7aa` | `#c2410c` | Start nodes, triggers, events |115| Neutral / Container | `#f1f5f9` | `#475569` | Groups, swimlanes, backgrounds |116117**Text colors:**118119| Level | Color |120|-------|-------|121| Title | `#1e293b` |122| Label | `#334155` |123| Description | `#64748b` |124125**Rule: Do not invent new colors.** Pick from this palette.126127### Arrow semantics128129| Style | Meaning |130|-------|---------|131| Solid (`strokeStyle: null`) | Primary flow, main path |132| Dashed (`"dashed"`) | Response, async, callback |133| Dotted (`"dotted"`) | Optional, reference, weak dependency |134135## Excalidraw JSON Structure136137### File skeleton138139```json140{141 "type": "excalidraw",142 "version": 2,143 "source": "claude-code",144 "elements": [],145 "appState": { "viewBackgroundColor": "#ffffff" }146}147```148149### Element types150151| type | use for |152|-----------|----------------------------------|153| rectangle | boxes, components, modules |154| ellipse | start/end nodes, databases |155| diamond | decision points |156| arrow | directed connections |157| line | undirected connections |158| text | standalone labels |159160### Element sizing161162Calculate element width from label text to prevent truncation:163164```165Latin text: width = max(160, charCount * 9)166CJK text: width = max(160, charCount * 18)167Mixed text: estimate each character individually, sum up168```169170Height: use `60` for single-line labels, add `24` per additional line.171172### Required properties (all elements)173174```json175{176 "id": "auth_service",177 "type": "rectangle",178 "x": 100, "y": 100,179 "width": 160, "height": 60,180 "angle": 0,181 "strokeColor": "#1e40af",182 "backgroundColor": "#dbeafe",183 "fillStyle": "solid",184 "strokeWidth": 2,185 "roughness": 0,186 "opacity": 100,187 "seed": 100001,188 "boundElements": [189 { "id": "arrow_to_db", "type": "arrow" },190 { "id": "label_auth", "type": "text" }191 ]192}193```194195Use **descriptive string IDs** (e.g., `"api_gateway"`, `"arrow_gw_to_auth"`) instead of random strings.196197Give each element a unique `seed` (integer). Namespace by section: 100xxx, 200xxx, 300xxx.198199### JSON field rules200201- `boundElements`: use `null` when empty, never `[]`202- `updated`: always use `1`, never timestamps203- Do NOT include: `frameId`, `index`, `versionNonce`, `rawText`204- `points` in arrows: always start at `[0, 0]`205- `seed`: must be a positive integer, unique per element206207### Text inside shapes (contained text)208209When text belongs inside a shape, bind them bidirectionally:210211```json212{213 "id": "label_auth",214 "type": "text",215 "text": "Auth Service",216 "fontSize": 20,217 "fontFamily": 2,218 "textAlign": "center",219 "verticalAlign": "middle",220 "strokeColor": "#1e293b",221 "containerId": "auth_service"222}223```224225**CRITICAL: Text `strokeColor` is the text color.** Always set it explicitly to a dark color from the text color palette. Never omit it — omitting `strokeColor` on text can cause invisible text that blends with the shape background.226227The parent shape must list the text in its `boundElements`:228```json229"boundElements": [{ "id": "label_auth", "type": "text" }]230```231232### Arrow binding (bidirectional)233234Arrows must bind to shapes, and shapes must reference bound arrows:235236```json237{238 "id": "arrow_gw_to_auth",239 "type": "arrow",240 "points": [[0, 0], [200, 0]],241 "startBinding": { "elementId": "api_gateway", "gap": 5, "focus": 0 },242 "endBinding": { "elementId": "auth_service", "gap": 5, "focus": 0 }243}244```245246Both `api_gateway` and `auth_service` must include in their `boundElements`:247```json248"boundElements": [{ "id": "arrow_gw_to_auth", "type": "arrow" }]249```250251### Arrow routing252253**L-shaped (elbow) arrows** — orthogonal routing with 3+ points:254255```json256"points": [[0, 0], [100, 0], [100, 150]]257```258259**Elbowed arrows** — automatic right-angle routing:260261```json262{263 "type": "arrow",264 "points": [[0, 0], [0, -50], [200, -50], [200, 0]],265 "elbowed": true266}267```268269**Curved arrows** — smooth routing with waypoints:270271```json272{273 "type": "arrow",274 "points": [[0, 0], [50, -40], [200, 0]],275 "roundness": { "type": 2 }276}277```278279### Grouping280281Related elements share `groupIds`. Nested groups list IDs innermost-first:282283```json284"groupIds": ["inner_group", "outer_group"]285```286287## Diagram Patterns288289Choose the right visual pattern for each diagram type.290291### Spacing Reference292293| Scenario | Spacing |294|----------|---------|295| Labeled arrow gap (between shapes) | 150–200px |296| Unlabeled arrow gap | 100–120px |297| Column spacing (labeled arrows) | 400px (220px box + 180px gap) |298| Column spacing (unlabeled arrows) | 340px (220px box + 120px gap) |299| Row spacing | 280–350px (150px box + 130–200px gap) |300| Zone/container padding | 50–60px around children |301| Zone/container opacity | 25–40 |302| Minimum gap between any elements | 40px |303304### Flowchart (LR or TB)305306- Ellipse for start/end, diamond for decisions, rectangle for process307- 200px horizontal spacing, 150px vertical spacing308- Decision branches: "Yes" goes forward, "No" goes down309- 3–10 steps (max 15)310311### Architecture / System Diagram312313- Column spacing per table above; use labeled arrow spacing when connections have labels314- Group related services in dashed `Neutral` containers (opacity: 30, padding: 50px)315- Gateway/entry at left or top, databases at right or bottom316- 3–8 entities (max 12)317318### Sequence Diagram319320- 200px between participants (rectangles at top)321- Vertical lifelines as dashed lines322- Horizontal arrows for messages, 60px vertical spacing323- Solid arrow = request, dashed arrow = response324325### Mind Map326327- Central node: largest (200x100), `Trigger` color328- Level 1: 150x70, `Primary` color, radial around center329- Level 2: 120x50, `Process` color330- Level 3: 90x40, `Neutral` color331- Use lines (not arrows) for connections332- 4–6 branches (max 8), 2–4 sub-topics per branch333334### Swimlane335336- Large transparent rectangles (`Neutral` fill, `"dashed"` stroke, opacity: 30) as lane boundaries337- Lane label as free-standing text at top-left of lane (not bound to rectangle), 28px font338- Elements flow left-to-right within lanes339- Arrows cross lanes for handoffs340341## Section-by-Section Construction342343For diagrams with **10+ elements**, do NOT generate the entire JSON at once. Build in sections:3443451. **Plan all sections first** — list element IDs, positions, and cross-section bindings3462. **Write section 1** — create the file with initial elements3473. **Append section 2** — read the file, add new elements to the `elements` array3484. **Repeat** — continue until all sections are done3495. **Final pass** — verify all `boundElements` and `startBinding`/`endBinding` references are consistent350351Namespace element seeds by section (100xxx, 200xxx, 300xxx) to avoid collisions.352353## Export354355### Option A: Kroki API (SVG only — zero install)356357```bash358# SVG via Kroki API359curl -s -X POST https://kroki.io/excalidraw/svg \360 -H "Content-Type: application/json" \361 --data-binary "@diagram.excalidraw" \362 -o diagram.svg363364# Via local Kroki Docker (offline)365curl -s -X POST http://localhost:8000/excalidraw/svg \366 -H "Content-Type: application/json" \367 --data-binary "@diagram.excalidraw" \368 -o diagram.svg369```370371### Option B: Local CLI (PNG + SVG)372373```bash374# PNG at 2x scale (recommended)375excalidraw-brute-export-cli -i diagram.excalidraw -o diagram.png -f png -s 2376377# PNG at 1x scale378excalidraw-brute-export-cli -i diagram.excalidraw -o diagram.png -f png -s 1379380# SVG381excalidraw-brute-export-cli -i diagram.excalidraw -o diagram.svg -f svg -s 1382```383384**Required flags:** `-f` (format: `png` or `svg`) and `-s` (scale: `1`, `2`, or `3`).385386## Anti-Patterns387388**Never put `text` on large background/zone rectangles.** Excalidraw centers text in the middle of the shape, overlapping contained elements. Instead, use a free-standing `text` element positioned at the top of the zone.389390**Avoid cross-zone arrows.** Long diagonal arrows create visual spaghetti. Route arrows within zones or along zone edges. If a cross-zone connection is unavoidable, route it along the perimeter.391392**Use arrow labels sparingly.** Labels placed at the arrow midpoint overlap on short arrows. Keep labels to ≤12 characters and ensure ≥120px clear space between connected shapes. Omit labels when the connection meaning is obvious from context.393394**Don't use filled backgrounds on containers that hold other elements.** Use `opacity: 30` (or 25-40 range) for zone/container rectangles so contained elements remain visible.395396**Always set explicit `strokeColor` on text elements.** Text `strokeColor` is the rendered text color. If omitted, text may inherit the parent shape's background color and become invisible. Use `#1e293b` (title), `#334155` (label), or `#64748b` (description) from the text color palette.397398## Common Mistakes399400| Mistake | Fix |401|---------|-----|402| Kroki returns error | Ensure file is valid JSON with `"type": "excalidraw"` and `"elements"` array |403| Kroki only outputs SVG | Use local CLI (`excalidraw-brute-export-cli`) for PNG |404| Export fails with "Missing required flag" | Always pass `-f png` and `-s 2` |405| Export fails with "Executable doesn't exist" | Run `npx playwright install firefox` |406| macOS: timeout waiting for file chooser | Apply the macOS Meta patch above |407| Arrow `points` not relative to origin | `points` always start at `[0,0]` |408| Missing `id` on elements | Use descriptive string IDs per element |409| Overlapping elements | Use spacing reference table; minimum 40px gap |410| Arrows not interactive in excalidraw.com | Add `boundElements` to shapes referencing all bound arrows/text |411| Text not centered in shape | Set `containerId` on text AND add text to shape's `boundElements` |412| All text same size | Use font size hierarchy: 28 → 24 → 20 → 16 → 14 |413| Diagram looks monotone | Apply semantic colors from the palette, follow 60-30-10 rule |414| Text invisible / same color as background | Always set `strokeColor` on text elements to a dark color (`#1e293b`, `#334155`, or `#64748b`) |415| Text overlaps inside zone/container | Don't bind text to zone rectangles; use free-standing text at top |416| Text truncated in shapes | Use width formula: `max(160, charCount * 9)`, double for CJK |417| `boundElements: []` causes issues | Use `null` for empty boundElements, never `[]` |418
Full transparency — inspect the skill content before installing.