Generate diagrams from structured JSON → SVG (and optionally PNG).
Install elkjs in the target directory (if not already present): bash
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: bash
# 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: markdown

json
{
"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
}
]
}
PropertyTypeDescriptionidstringRequired. Unique identifierwidthnumberNode width in px (default 120)heightnumberNode height in px (default 40)labelsarray[{text}] — used by ELK for layout calculationlabelstringDisplay text rendered in SVG (supports emoji). Falls back to id``colorstringColor key from palette (see below)subtitlestringSmaller text below the labelfontSizenumberLabel font size (default 13)childrenarrayNested nodes — makes this a containercontainerColorstringColor key for container background
PropertyTypeDescriptionidstringRequired. Unique identifiersourcesstring[]Source node id(s)targetsstring[]Target node id(s)labelsarray[{text, width, height}] — edge labelsedgeColorstringHex color (default #64748B)dashedbooleanDashed line stylestrokeWidthnumberLine thickness (default 1.5)
8 semantic colors. Every node maps to one. A legend auto-renders at the bottom of each SVG.
KeyColorMeaningUse Foraction🔵 BlueSystem actionSteps the app performs — API calls, DB writes, cron triggersexternal🟢 TealExternal serviceThird-party APIs — Google, Twilio, Stripe, Resenddecision🩷 PinkDecision pointYes/no branches, if/else, conditional checksuser🟠 OrangeUser actionThings the user does — clicks, inputs, reviewssuccess🟢 GreenPositive outcomeDone, confirmed, created, visiblenegative🔴 RedNegative outcomeCanceled, error, failed, not builtneutral⚫ GrayNeutral / infoStarting points, labels, inactive, informationaldata🟡 AmberData / artifactRecords, drafts, outputs, intermediate data
Set "legend": false on the root graph to hide the auto-legend.
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]context (pink) color for yes/no branching.width/ height for proper positioning.children array to a node. Use containerColor: "step" for a light blue group.dashed: true on edges for alternative/optional paths.title on the root graph for a rendered heading above the diagram.RIGHT direction can produce overlapping layouts. Prefer DOWN for containers, or flatten to a non-container layout if horizontal.labels vs label: labels (array) is what ELK uses for layout spacing. label (string) is what gets rendered in the SVG. Always set both — labels[0].text should approximate the display label length for correct sizing.package.json must have "type": "module" for the ESM import to work.