Diagrams
Generate diagrams from structured JSON → SVG (and optionally PNG).
Quick Start
-
Install elkjs in the target directory (if not already present):
cd <project>/docs/diagrams && npm init -y && npm install elkjs # Set "type": "module" in package.json -
Write ELK JSON files describing the diagram (see schema below)
-
Render:
# Single file node <skill-dir>/scripts/render-elk.mjs diagram.json output.svg # Batch: all .json files in a folder → svg/ subfolder node <skill-dir>/scripts/render-elk.mjs --dir <folder> # Batch + PNG (macOS only, uses sips) node <skill-dir>/scripts/render-elk.mjs --dir <folder> --png -
Embed in markdown:

ELK JSON Schema
{
"id": "root",
"title": "Diagram Title (rendered as heading)",
"layoutOptions": {
"elk.algorithm": "layered",
"elk.direction": "DOWN",
"elk.spacing.nodeNode": "30",
"elk.layered.spacing.nodeNodeBetweenLayers": "40",
"elk.padding": "[top=40,left=20,bottom=20,right=20]"
},
"children": [
{
"id": "node1",
"width": 220,
"height": 45,
"labels": [{"text": "Node label for ELK layout"}],
"label": "📦 Display label (rendered in SVG)",
"color": "core",
"subtitle": "Optional second line"
}
],
"edges": [
{
"id": "e1",
"sources": ["node1"],
"targets": ["node2"],
"labels": [{"text": "Yes", "width": 25, "height": 14}],
"edgeColor": "#10B981",
"dashed": true
}
]
}
Node Properties
| Property | Type | Description |
|---|---|---|
id | string | Required. Unique identifier |
width | number | Node width in px (default 120) |
height | number | Node height in px (default 40) |
labels | array | [{text}] — used by ELK for layout calculation |
label | string | Display text rendered in SVG (supports emoji). Falls back to id |
color | string | Color key from palette (see below) |
subtitle | string | Smaller text below the label |
fontSize | number | Label font size (default 13) |
children | array | Nested nodes — makes this a container |
containerColor | string | Color key for container background |
Edge Properties
| Property | Type | Description |
|---|---|---|
id | string | Required. Unique identifier |
sources | string[] | Source node id(s) |
targets | string[] | Target node id(s) |
labels | array | [{text, width, height}] — edge labels |
edgeColor | string | Hex color (default #64748B) |
dashed | boolean | Dashed line style |
strokeWidth | number | Line thickness (default 1.5) |
Color Palette
8 semantic colors. Every node maps to one. A legend auto-renders at the bottom of each SVG.
| Key | Color | Meaning | Use For |
|---|---|---|---|
action | 🔵 Blue | System action | Steps the app performs — API calls, DB writes, cron triggers |
external | 🟢 Teal | External service | Third-party APIs — Google, Twilio, Stripe, Resend |
decision | 🩷 Pink | Decision point | Yes/no branches, if/else, conditional checks |
user | 🟠 Orange | User action | Things the user does — clicks, inputs, reviews |
success | 🟢 Green | Positive outcome | Done, confirmed, created, visible |
negative | 🔴 Red | Negative outcome | Canceled, error, failed, not built |
neutral | ⚫ Gray | Neutral / info | Starting points, labels, inactive, informational |
data | 🟡 Amber | Data / artifact | Records, drafts, outputs, intermediate data |
Set "legend": false on the root graph to hide the auto-legend.
Layout Options
Common layoutOptions values:
elk.direction:DOWN(default),RIGHT,LEFT,UPelk.algorithm:layered(default, best for flowcharts),force,stresselk.spacing.nodeNode: Space between sibling nodes (px)elk.layered.spacing.nodeNodeBetweenLayers: Space between layers (px)elk.padding:[top=N,left=N,bottom=N,right=N]
Design Tips
- Sizing: 200-280px wide for most nodes. 45px tall for single-line, 55px for two-line labels.
- Decision nodes: Use
context(pink) color for yes/no branching. - Edge labels: Keep short (Yes/No/Error). Set
width/heightfor proper positioning. - Containers: Add
childrenarray to a node. UsecontainerColor: "step"for a light blue group. - Manual trips vs automated: Use
dashed: trueon edges for alternative/optional paths. - Title: Set
titleon the root graph for a rendered heading above the diagram. - Emoji in labels: Supported and encouraged for visual scanning.
Gotchas
- Container layout: ELK's layered algorithm with nested containers in
RIGHTdirection can produce overlapping layouts. PreferDOWNfor containers, or flatten to a non-container layout if horizontal. labelsvslabel:labels(array) is what ELK uses for layout spacing.label(string) is what gets rendered in the SVG. Always set both —labels[0].textshould approximate the display label length for correct sizing.package.jsonmust have"type": "module"for the ESM import to work.- elkjs must be installed locally in the directory where you run the script. It's not global.