Excalidraw diagrams
You produce diagrams by writing a .excalidraw JSON file into the workspace. The
file IS the diagram — Shockwave renders .excalidraw files in an editable canvas.
There is no render/preview command and no API to call: write the file, tell the
user its name, and they open it from the file tree.
Workflow
- Decide the elements and a rough layout (positions, sizes) before writing.
- Write a
.excalidraw file with a descriptive name into the relevant folder —
default to the same folder as the file under discussion, else the workspace
root. Example: Auth flow.excalidraw.
- To EDIT an existing diagram, read the file, modify the
elements array, and
write it back. If the file is open in Shockwave, the canvas reloads live.
Use your normal file-writing tools. Always write valid JSON (no comments, no
trailing commas) or the canvas can't open it.
File envelope
{
"type": "excalidraw",
"version": 2,
"source": "shockwave",
"elements": [ /* ordered back-to-front; later elements draw on top */ ],
"appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },
"files": {}
}
Element schema
Every element — whatever its type — must include ALL of these fields. Missing
fields can make the canvas drop the element or fail to open.
{
"id": "unique-string",
"type": "rectangle",
"x": 100, "y": 100, "width": 200, "height": 100,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"roundness": { "type": 3 },
"seed": 12345,
"version": 1,
"versionNonce": 1,
"isDeleted": false,
"boundElements": [],
"updated": 1,
"link": null,
"locked": false
}
id — any string, unique within the file. Use readable ids (box-auth, arrow-1).
seed / versionNonce — any integers; give each element different values.
roundness — { "type": 3 } for rounded corners, or null for sharp. Ellipses use null.
- Colors — use Excalidraw's palette: stroke
#1e1e1e (black), #1971c2 (blue),
#e03131 (red), #2f9e44 (green), #f08c00 (orange), #9c36b5 (purple).
Backgrounds are the light tints: #a5d8ff, #ffc9c9, #b2f2bb, #ffec99, #eebefa, or transparent.
Shapes
rectangle, ellipse, diamond — use the fields above as-is.
Text
{
"id": "label-1", "type": "text",
"x": 120, "y": 130, "width": 160, "height": 25, "angle": 0,
"strokeColor": "#1e1e1e", "backgroundColor": "transparent",
"fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid",
"roughness": 1, "opacity": 100, "groupIds": [], "frameId": null,
"roundness": null, "seed": 222, "version": 1, "versionNonce": 222,
"isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false,
"text": "Login", "fontSize": 20, "fontFamily": 1,
"textAlign": "center", "verticalAlign": "middle",
"containerId": null, "originalText": "Login", "lineHeight": 1.25
}
fontFamily: 1 = hand-drawn (Excalifont), 2 = normal, 3 = code.
- Estimate
width ≈ text.length * fontSize * 0.6, height ≈ fontSize * 1.25.
To center a label INSIDE a shape, bind them: set the text's containerId to the
shape's id, and add { "id": "<text-id>", "type": "text" } to the shape's
boundElements. The canvas then auto-centers the text in the shape.
Arrows and lines
{
"id": "arrow-1", "type": "arrow",
"x": 300, "y": 150, "width": 80, "height": 0, "angle": 0,
"strokeColor": "#1e1e1e", "backgroundColor": "transparent",
"fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid",
"roughness": 1, "opacity": 100, "groupIds": [], "frameId": null,
"roundness": { "type": 2 }, "seed": 333, "version": 1, "versionNonce": 333,
"isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false,
"points": [[0, 0], [80, 0]],
"startBinding": { "elementId": "box-a", "focus": 0, "gap": 4 },
"endBinding": { "elementId": "box-b", "focus": 0, "gap": 4 },
"startArrowhead": null, "endArrowhead": "arrow"
}
points are RELATIVE to the arrow's x/y; first is usually [0,0].
width/height is the bounding box of the points.
- To connect two shapes, set
startBinding/endBinding to their ids AND add
{ "id": "<arrow-id>", "type": "arrow" } to BOTH shapes' boundElements.
Then the arrow stays attached when shapes move. Use focus: 0, gap: 4 as
safe defaults. Omit bindings (null) for free-floating arrows.
Layout rules
- Lay shapes on a grid; leave ≥ 60px gaps so arrows have room and nothing overlaps.
- Standard box: ~160–220 wide, ~80–100 tall. Keep sizes consistent.
- Flow left→right or top→bottom. Put arrowheads on the destination end.
- Label every arrow that isn't obvious (a small
text element near its midpoint).
- Order
elements back-to-front: shapes first, then arrows, then text/labels on top.
Worked example — two boxes and a labelled arrow
{
"type": "excalidraw", "version": 2, "source": "shockwave",
"elements": [
{ "id": "box-a", "type": "rectangle", "x": 100, "y": 100, "width": 180, "height": 90, "angle": 0, "strokeColor": "#1971c2", "backgroundColor": "#a5d8ff", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": { "type": 3 }, "seed": 11, "version": 1, "versionNonce": 11, "isDeleted": false, "boundElements": [{ "id": "t-a", "type": "text" }, { "id": "arrow-1", "type": "arrow" }], "updated": 1, "link": null, "locked": false },
{ "id": "t-a", "type": "text", "x": 130, "y": 132, "width": 120, "height": 25, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": null, "seed": 12, "version": 1, "versionNonce": 12, "isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false, "text": "Client", "fontSize": 20, "fontFamily": 1, "textAlign": "center", "verticalAlign": "middle", "containerId": "box-a", "originalText": "Client", "lineHeight": 1.25 },
{ "id": "box-b", "type": "rectangle", "x": 420, "y": 100, "width": 180, "height": 90, "angle": 0, "strokeColor": "#2f9e44", "backgroundColor": "#b2f2bb", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": { "type": 3 }, "seed": 21, "version": 1, "versionNonce": 21, "isDeleted": false, "boundElements": [{ "id": "t-b", "type": "text" }, { "id": "arrow-1", "type": "arrow" }], "updated": 1, "link": null, "locked": false },
{ "id": "t-b", "type": "text", "x": 450, "y": 132, "width": 120, "height": 25, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": null, "seed": 22, "version": 1, "versionNonce": 22, "isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false, "text": "Server", "fontSize": 20, "fontFamily": 1, "textAlign": "center", "verticalAlign": "middle", "containerId": "box-b", "originalText": "Server", "lineHeight": 1.25 },
{ "id": "arrow-1", "type": "arrow", "x": 282, "y": 145, "width": 136, "height": 0, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": { "type": 2 }, "seed": 31, "version": 1, "versionNonce": 31, "isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false, "points": [[0, 0], [136, 0]], "startBinding": { "elementId": "box-a", "focus": 0, "gap": 4 }, "endBinding": { "elementId": "box-b", "focus": 0, "gap": 4 }, "startArrowhead": null, "endArrowhead": "arrow" }
],
"appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },
"files": {}
}
After writing, tell the user the filename and that they can open it from the file
tree to view and edit it.
1---2name: excalidraw3description: Create and edit hand-drawn-style diagrams as Excalidraw files in the workspace. Use this skill whenever the user wants to draw, diagram, sketch, or visualize something — a flowchart, architecture diagram, system design, sequence of steps, mind map, wireframe, org chart, entity-relationship sketch, or any boxes-and-arrows picture. Also use when they say "draw me a…", "make a diagram of…", "sketch out…", "visualize…", "show this as a diagram", or ask to modify an existing `.excalidraw` file. Write the diagram as a `.excalidraw` file in the workspace; the user opens it in Shockwave's drawing canvas, where it is fully editable. Do NOT use for prose, tables, or code — only for visual diagrams.4---56# Excalidraw diagrams78You produce diagrams by writing a `.excalidraw` JSON file into the workspace. The9file IS the diagram — Shockwave renders `.excalidraw` files in an editable canvas.10There is no render/preview command and no API to call: write the file, tell the11user its name, and they open it from the file tree.1213## Workflow14151. Decide the elements and a rough layout (positions, sizes) before writing.162. Write a `.excalidraw` file with a descriptive name into the relevant folder —17 default to the same folder as the file under discussion, else the workspace18 root. Example: `Auth flow.excalidraw`.193. To EDIT an existing diagram, read the file, modify the `elements` array, and20 write it back. If the file is open in Shockwave, the canvas reloads live.2122Use your normal file-writing tools. Always write valid JSON (no comments, no23trailing commas) or the canvas can't open it.2425## File envelope2627```json28{29 "type": "excalidraw",30 "version": 2,31 "source": "shockwave",32 "elements": [ /* ordered back-to-front; later elements draw on top */ ],33 "appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },34 "files": {}35}36```3738## Element schema3940Every element — whatever its type — must include ALL of these fields. Missing41fields can make the canvas drop the element or fail to open.4243```json44{45 "id": "unique-string",46 "type": "rectangle",47 "x": 100, "y": 100, "width": 200, "height": 100,48 "angle": 0,49 "strokeColor": "#1e1e1e",50 "backgroundColor": "transparent",51 "fillStyle": "solid",52 "strokeWidth": 2,53 "strokeStyle": "solid",54 "roughness": 1,55 "opacity": 100,56 "groupIds": [],57 "frameId": null,58 "roundness": { "type": 3 },59 "seed": 12345,60 "version": 1,61 "versionNonce": 1,62 "isDeleted": false,63 "boundElements": [],64 "updated": 1,65 "link": null,66 "locked": false67}68```6970- `id` — any string, unique within the file. Use readable ids (`box-auth`, `arrow-1`).71- `seed` / `versionNonce` — any integers; give each element different values.72- `roundness` — `{ "type": 3 }` for rounded corners, or `null` for sharp. Ellipses use `null`.73- Colors — use Excalidraw's palette: stroke `#1e1e1e` (black), `#1971c2` (blue),74 `#e03131` (red), `#2f9e44` (green), `#f08c00` (orange), `#9c36b5` (purple).75 Backgrounds are the light tints: `#a5d8ff`, `#ffc9c9`, `#b2f2bb`, `#ffec99`, `#eebefa`, or `transparent`.7677### Shapes7879`rectangle`, `ellipse`, `diamond` — use the fields above as-is.8081### Text8283```json84{85 "id": "label-1", "type": "text",86 "x": 120, "y": 130, "width": 160, "height": 25, "angle": 0,87 "strokeColor": "#1e1e1e", "backgroundColor": "transparent",88 "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid",89 "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null,90 "roundness": null, "seed": 222, "version": 1, "versionNonce": 222,91 "isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false,92 "text": "Login", "fontSize": 20, "fontFamily": 1,93 "textAlign": "center", "verticalAlign": "middle",94 "containerId": null, "originalText": "Login", "lineHeight": 1.2595}96```9798- `fontFamily`: `1` = hand-drawn (Excalifont), `2` = normal, `3` = code.99- Estimate `width` ≈ `text.length * fontSize * 0.6`, `height` ≈ `fontSize * 1.25`.100101To center a label INSIDE a shape, bind them: set the text's `containerId` to the102shape's `id`, and add `{ "id": "<text-id>", "type": "text" }` to the shape's103`boundElements`. The canvas then auto-centers the text in the shape.104105### Arrows and lines106107```json108{109 "id": "arrow-1", "type": "arrow",110 "x": 300, "y": 150, "width": 80, "height": 0, "angle": 0,111 "strokeColor": "#1e1e1e", "backgroundColor": "transparent",112 "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid",113 "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null,114 "roundness": { "type": 2 }, "seed": 333, "version": 1, "versionNonce": 333,115 "isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false,116 "points": [[0, 0], [80, 0]],117 "startBinding": { "elementId": "box-a", "focus": 0, "gap": 4 },118 "endBinding": { "elementId": "box-b", "focus": 0, "gap": 4 },119 "startArrowhead": null, "endArrowhead": "arrow"120}121```122123- `points` are RELATIVE to the arrow's `x`/`y`; first is usually `[0,0]`.124- `width`/`height` is the bounding box of the points.125- To connect two shapes, set `startBinding`/`endBinding` to their ids AND add126 `{ "id": "<arrow-id>", "type": "arrow" }` to BOTH shapes' `boundElements`.127 Then the arrow stays attached when shapes move. Use `focus: 0`, `gap: 4` as128 safe defaults. Omit bindings (`null`) for free-floating arrows.129130## Layout rules131132- Lay shapes on a grid; leave ≥ 60px gaps so arrows have room and nothing overlaps.133- Standard box: ~160–220 wide, ~80–100 tall. Keep sizes consistent.134- Flow left→right or top→bottom. Put arrowheads on the destination end.135- Label every arrow that isn't obvious (a small `text` element near its midpoint).136- Order `elements` back-to-front: shapes first, then arrows, then text/labels on top.137138## Worked example — two boxes and a labelled arrow139140```json141{142 "type": "excalidraw", "version": 2, "source": "shockwave",143 "elements": [144 { "id": "box-a", "type": "rectangle", "x": 100, "y": 100, "width": 180, "height": 90, "angle": 0, "strokeColor": "#1971c2", "backgroundColor": "#a5d8ff", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": { "type": 3 }, "seed": 11, "version": 1, "versionNonce": 11, "isDeleted": false, "boundElements": [{ "id": "t-a", "type": "text" }, { "id": "arrow-1", "type": "arrow" }], "updated": 1, "link": null, "locked": false },145 { "id": "t-a", "type": "text", "x": 130, "y": 132, "width": 120, "height": 25, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": null, "seed": 12, "version": 1, "versionNonce": 12, "isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false, "text": "Client", "fontSize": 20, "fontFamily": 1, "textAlign": "center", "verticalAlign": "middle", "containerId": "box-a", "originalText": "Client", "lineHeight": 1.25 },146 { "id": "box-b", "type": "rectangle", "x": 420, "y": 100, "width": 180, "height": 90, "angle": 0, "strokeColor": "#2f9e44", "backgroundColor": "#b2f2bb", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": { "type": 3 }, "seed": 21, "version": 1, "versionNonce": 21, "isDeleted": false, "boundElements": [{ "id": "t-b", "type": "text" }, { "id": "arrow-1", "type": "arrow" }], "updated": 1, "link": null, "locked": false },147 { "id": "t-b", "type": "text", "x": 450, "y": 132, "width": 120, "height": 25, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": null, "seed": 22, "version": 1, "versionNonce": 22, "isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false, "text": "Server", "fontSize": 20, "fontFamily": 1, "textAlign": "center", "verticalAlign": "middle", "containerId": "box-b", "originalText": "Server", "lineHeight": 1.25 },148 { "id": "arrow-1", "type": "arrow", "x": 282, "y": 145, "width": 136, "height": 0, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "frameId": null, "roundness": { "type": 2 }, "seed": 31, "version": 1, "versionNonce": 31, "isDeleted": false, "boundElements": [], "updated": 1, "link": null, "locked": false, "points": [[0, 0], [136, 0]], "startBinding": { "elementId": "box-a", "focus": 0, "gap": 4 }, "endBinding": { "elementId": "box-b", "focus": 0, "gap": 4 }, "startArrowhead": null, "endArrowhead": "arrow" }149 ],150 "appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },151 "files": {}152}153```154155After writing, tell the user the filename and that they can open it from the file156tree to view and edit it.