# Excalidraw

> Generate architecture diagrams as .excalidraw files.

- Skill: `indiosmo/excalidraw` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add indiosmo/excalidraw`
- Raw SKILL.md: https://api.skillmd.com/api/skills/indiosmo/excalidraw/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: indiosmo (https://skillmd.com/u/indiosmo)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/indiosmo/excalidraw

---


# Excalidraw Architecture Diagram Generator

Generate architecture diagrams as `.excalidraw` files from codebase analysis or user specifications.

---

## Prerequisites

- No prerequisites - generates raw JSON
- For visual inspection: Open `.excalidraw` files in [excalidraw.com](https://excalidraw.com) or VS Code extension

---

## Quick Start

**User asks:**
```
"Generate an architecture diagram for this project"
"Create an excalidraw diagram of the system"
"Visualize this codebase structure"
"Draw the infrastructure architecture"
```

**Claude will:**
1. Analyze the codebase (any language/framework)
2. Identify components, services, databases, APIs
3. Map relationships and data flows
4. Generate valid `.excalidraw` JSON
5. Validate the structure
6. User opens in excalidraw.com or VS Code for visual inspection

---

## Critical Rules

### 1. Avoid Diamond Shapes (arrows break)

Diamond arrow connections are broken in raw Excalidraw JSON because Excalidraw applies `roundness` to diamond vertices during rendering, causing arrows to appear disconnected. Use styled rectangles instead:

| Semantic Meaning | Rectangle Style |
|------------------|-----------------|
| Orchestrator/Hub | Coral (`#ffa8a8`/`#c92a2a`) + strokeWidth: 3 |
| Decision Point | Orange (`#ffd8a8`/`#e8590c`) + dashed stroke |

### 2. Labels Require Two Elements

The `label` property does not work in raw JSON. Every labeled shape needs:

```json
// 1. Shape with boundElements reference
{
  "id": "my-box",
  "type": "rectangle",
  "boundElements": [{ "type": "text", "id": "my-box-text" }]
}

// 2. Separate text element with containerId
{
  "id": "my-box-text",
  "type": "text",
  "containerId": "my-box",
  "text": "My Label"
}
```

### 3. Elbow Arrows Need Three Properties Together

For 90-degree corners (not curved), all three are required:

```json
{
  "type": "arrow",
  "roughness": 0,        // Clean lines
  "roundness": null,     // Sharp corners
  "elbowed": true        // 90-degree mode
}
```

### 4. Arrow Position at Shape Edge

Arrows should start/end at shape edges, not centers:

| Edge | Formula |
|------|---------|
| Top | `(x + width/2, y)` |
| Bottom | `(x + width/2, y + height)` |
| Left | `(x, y + height/2)` |
| Right | `(x + width, y + height/2)` |

### 5. Arrow width/height = Bounding Box

```
points = [[0, 0], [-440, 0], [-440, 70]]
width = 440   // max(abs(point[0]))
height = 70   // max(abs(point[1]))
```

### 6. Arrow Bindings (optional but recommended)

For better visual attachment, arrows can use `startBinding` and `endBinding` to anchor to specific shapes. This keeps arrows connected when elements are repositioned. When using bindings, also add the arrow to the shape's `boundElements` array. See `references/arrows.md` for binding properties and `fixedPoint` values.

---

## Minimal Working Example

Two boxes connected by an arrow (copy and adapt for any diagram):

```json
{
  "type": "excalidraw",
  "version": 2,
  "source": "claude-code-excalidraw-skill",
  "elements": [
    {
      "id": "box-a",
      "type": "rectangle",
      "x": 100,
      "y": 100,
      "width": 160,
      "height": 80,
      "angle": 0,
      "strokeColor": "#1971c2",
      "backgroundColor": "#a5d8ff",
      "fillStyle": "solid",
      "strokeWidth": 2,
      "strokeStyle": "solid",
      "roughness": 1,
      "opacity": 100,
      "groupIds": [],
      "frameId": null,
      "roundness": { "type": 3 },
      "seed": 1,
      "version": 1,
      "versionNonce": 1,
      "isDeleted": false,
      "boundElements": [{ "type": "text", "id": "box-a-text" }],
      "updated": 1,
      "link": null,
      "locked": false
    },
    {
      "id": "box-a-text",
      "type": "text",
      "x": 105,
      "y": 125,
      "width": 150,
      "height": 30,
      "angle": 0,
      "strokeColor": "#1e1e1e",
      "backgroundColor": "transparent",
      "fillStyle": "solid",
      "strokeWidth": 1,
      "strokeStyle": "solid",
      "roughness": 1,
      "opacity": 100,
      "groupIds": [],
      "frameId": null,
      "roundness": null,
      "seed": 2,
      "version": 1,
      "versionNonce": 2,
      "isDeleted": false,
      "boundElements": null,
      "updated": 1,
      "link": null,
      "locked": false,
      "text": "Frontend",
      "fontSize": 16,
      "fontFamily": 1,
      "textAlign": "center",
      "verticalAlign": "middle",
      "containerId": "box-a",
      "originalText": "Frontend",
      "lineHeight": 1.25
    },
    {
      "id": "box-b",
      "type": "rectangle",
      "x": 100,
      "y": 280,
      "width": 160,
      "height": 80,
      "angle": 0,
      "strokeColor": "#7048e8",
      "backgroundColor": "#d0bfff",
      "fillStyle": "solid",
      "strokeWidth": 2,
      "strokeStyle": "solid",
      "roughness": 1,
      "opacity": 100,
      "groupIds": [],
      "frameId": null,
      "roundness": { "type": 3 },
      "seed": 3,
      "version": 1,
      "versionNonce": 3,
      "isDeleted": false,
      "boundElements": [{ "type": "text", "id": "box-b-text" }],
      "updated": 1,
      "link": null,
      "locked": false
    },
    {
      "id": "box-b-text",
      "type": "text",
      "x": 105,
      "y": 305,
      "width": 150,
      "height": 30,
      "angle": 0,
      "strokeColor": "#1e1e1e",
      "backgroundColor": "transparent",
      "fillStyle": "solid",
      "strokeWidth": 1,
      "strokeStyle": "solid",
      "roughness": 1,
      "opacity": 100,
      "groupIds": [],
      "frameId": null,
      "roundness": null,
      "seed": 4,
      "version": 1,
      "versionNonce": 4,
      "isDeleted": false,
      "boundElements": null,
      "updated": 1,
      "link": null,
      "locked": false,
      "text": "API Server",
      "fontSize": 16,
      "fontFamily": 1,
      "textAlign": "center",
      "verticalAlign": "middle",
      "containerId": "box-b",
      "originalText": "API Server",
      "lineHeight": 1.25
    },
    {
      "id": "arrow-a-b",
      "type": "arrow",
      "x": 180,
      "y": 180,
      "width": 0,
      "height": 100,
      "angle": 0,
      "strokeColor": "#1971c2",
      "backgroundColor": "transparent",
      "fillStyle": "solid",
      "strokeWidth": 2,
      "strokeStyle": "solid",
      "roughness": 0,
      "opacity": 100,
      "groupIds": [],
      "frameId": null,
      "roundness": null,
      "seed": 5,
      "version": 1,
      "versionNonce": 5,
      "isDeleted": false,
      "boundElements": null,
      "updated": 1,
      "link": null,
      "locked": false,
      "points": [[0, 0], [0, 100]],
      "lastCommittedPoint": null,
      "startBinding": null,
      "endBinding": null,
      "startArrowhead": null,
      "endArrowhead": "arrow",
      "elbowed": true
    }
  ],
  "appState": {
    "gridSize": 20,
    "viewBackgroundColor": "#ffffff"
  },
  "files": {}
}
```

Key structure notes:
- Each labeled shape needs two elements (shape + text with `containerId`)
- Arrow `x,y` is at source edge: `(100 + 160/2, 100 + 80)` = `(180, 180)`
- Arrow `height` matches the vertical distance: `280 - 180` = `100`
- Elbow arrows: `roughness: 0`, `roundness: null`, `elbowed: true`

---

## Workflow

### Step 1: Analyze Codebase

Discover components by looking for:

| Codebase Type | What to Look For |
|---------------|------------------|
| Monorepo | `packages/*/package.json`, workspace configs |
| Microservices | `docker-compose.yml`, k8s manifests |
| IaC | Terraform/Pulumi resource definitions |
| Backend API | Route definitions, controllers, DB models |
| Frontend | Component hierarchy, API calls |

**Use tools:**
- `Glob` for `**/package.json`, `**/Dockerfile`, `**/*.tf`
- `Grep` for `app.get`, `@Controller`, `CREATE TABLE`
- `Read` for README, config files, entry points

### Step 2: Plan Layout

**Vertical flow (most common):**
```
Row 1: Users/Entry points (y: 100)
Row 2: Frontend/Gateway (y: 250)
Row 3: Orchestration (y: 400)
Row 4: Services (y: 550)
Row 5: Data layer (y: 700)

Columns: x = 100, 300, 500, 700, 900
Element size: 160-200px x 80-90px
```

**Scaling layout for different component counts:**

| Components | Spacing Adjustment |
|------------|--------------------|
| 1-3 per row | Use default 200px column spacing and 150px row spacing. Center elements horizontally. |
| 4-6 per row | Keep 200px column spacing. Canvas will be wider; this is fine. |
| 7+ per row | Reduce column spacing to 180px, or split into two rows at the same logical tier. |
| Few rows (2-3) | Increase row spacing to 200px so the diagram does not look cramped vertically. |
| Many rows (6+) | Reduce row spacing to 130px, or use smaller element heights (70px) to keep the diagram compact. |

General principle: maintain at least 50px of clear space between any two elements. When a tier has many more components than others, widen that tier and center narrower tiers above/below it.

### Step 3: Generate Elements

For each component:
1. Create shape with unique `id`
2. Add `boundElements` referencing text
3. Create text with `containerId`
4. Choose color based on type (see `references/colors.md`)

### Step 4: Add Connections

For each relationship:
1. Calculate source edge point
2. Calculate target edge point
3. Determine routing pattern
4. Create arrow with `points` array
5. Set width/height from bounding box

See `references/arrows.md` for routing patterns.

### Step 5: Validate

Run validation script before finalizing:

```bash
uv run scripts/validate_excalidraw.py diagram.excalidraw
```

Checks performed:
- Valid JSON structure
- boundElements/containerId pairs match
- No duplicate IDs
- No diamond shapes
- Elbow arrows have required properties
- Arrow width/height matches points

### Step 6: Visual Inspection and Iterate

Open the `.excalidraw` file for visual inspection:
- **Web**: Drag file to [excalidraw.com](https://excalidraw.com)
- **VS Code**: Install "Excalidraw" extension and open file

Note: Claude cannot see the visual output. The user must review the diagram and report any issues.

If the user reports issues:
1. Identify the problem element from their description
2. Fix the JSON
3. Re-validate
4. Ask user to re-inspect
5. Repeat until correct

---

## Validation Checklist

Before writing file:
- [ ] Every shape with label has boundElements + text element
- [ ] Text elements have containerId matching shape
- [ ] Multi-point arrows have `elbowed: true`, `roundness: null`, `roughness: 0`
- [ ] Arrow x,y = source shape edge point
- [ ] Arrow final point offset reaches target edge
- [ ] Arrow width/height = bounding box of points
- [ ] No diamond shapes
- [ ] No duplicate IDs
- [ ] File is valid JSON

---

## Common Issues

| Issue | Fix |
|-------|-----|
| Labels don't appear | Use two elements (shape + text), not `label` property |
| Arrows curved | Add `elbowed: true`, `roundness: null`, `roughness: 0` |
| Arrows floating | Calculate x,y from shape edge, not center |
| Arrows overlapping | Stagger start positions across edge |
| Arrows clipped | Set width/height to bounding box of points |

---

## Reference Files

| File | Contents |
|------|----------|
| `references/schema.md` | JSON format, element types, text bindings, frames |
| `references/arrows.md` | Routing algorithm, patterns, bindings, staggering |
| `references/colors.md` | Default, AWS, Azure, GCP, Kubernetes palettes |

---

## Scripts

| Script | Purpose |
|--------|---------|
| `scripts/validate_excalidraw.py` | Validate JSON structure before saving |

Usage:
```bash
# Validate
uv run scripts/validate_excalidraw.py diagram.excalidraw

# Validate with verbose arrow checks
uv run scripts/validate_excalidraw.py diagram.excalidraw --verbose

# Output as JSON
uv run scripts/validate_excalidraw.py diagram.excalidraw --json
```

---

## Output

- **Location:** `docs/architecture/` or user-specified
- **Filename:** Descriptive, e.g., `system-architecture.excalidraw`
- **Testing:** Open in https://excalidraw.com or VS Code extension

