Skip to content

Instantly share code, notes, and snippets.

@fpytloun
Created July 8, 2026 08:49
Show Gist options
  • Select an option

  • Save fpytloun/0fa20211d03c61884dcc90a6b59c58c9 to your computer and use it in GitHub Desktop.

Select an option

Save fpytloun/0fa20211d03c61884dcc90a6b59c58c9 to your computer and use it in GitHub Desktop.
Excalidraw skill
name excalidraw
description Excalidraw diagram authoring — JSON format, element types, styling conventions, color palettes, and SVG/PNG generation via CLI. Use when creating or editing .excalidraw diagram files programmatically.
license MIT
metadata
source extracted audience
excalidraw.com
2026-03-26
developers

Excalidraw Diagram Authoring

Instructions for creating professional Excalidraw diagrams programmatically as JSON files and converting them to SVG/PNG.


File Format

Excalidraw files (.excalidraw) are JSON with this top-level structure:

{
  "type": "excalidraw",
  "version": 2,
  "source": "https://excalidraw.com",
  "elements": [ ... ],
  "appState": {
    "gridSize": null,
    "viewBackgroundColor": "#ffffff"
  },
  "files": {}
}

The elements array contains all shapes, text, and arrows. Order matters — later elements render on top.


Element Types

Common Properties (all elements)

Every element MUST have these properties:

{
  "id": "unique-id",
  "type": "rectangle",
  "x": 100, "y": 100, "width": 200, "height": 50,
  "strokeColor": "#1e1e1e",
  "backgroundColor": "#e9ecef",
  "fillStyle": "solid",
  "strokeWidth": 1,
  "strokeStyle": "solid",
  "roughness": 0,
  "opacity": 100,
  "angle": 0,
  "groupIds": [],
  "roundness": { "type": 3 },
  "seed": 1001,
  "version": 1,
  "versionNonce": 1001,
  "isDeleted": false,
  "boundElements": null,
  "updated": 1,
  "link": null,
  "locked": false
}

Key fields:

  • id — unique string identifier. Use descriptive kebab-case names (e.g., srv01-vector, arrow-stn-smb).
  • seed — arbitrary integer, must be unique per element. Use incrementing numbers (1001, 1002, ...).
  • versionNonce — set equal to seed for new elements.
  • roughnessalways use 0 for clean, professional lines. Value 1 gives hand-drawn look.
  • roundness{ "type": 3 } for rounded rectangles, { "type": 2 } for arrows/diamonds, null for text.
  • boundElements — array of { "id": "arrow-id", "type": "arrow" } for elements that arrows connect to. Set to null if no arrows connect.

Rectangle

{
  "type": "rectangle",
  "x": 100, "y": 100, "width": 200, "height": 50,
  "roundness": { "type": 3 }
}

Use rectangles for: containers/groups, service boxes, callout banners.

Diamond

{
  "type": "diamond",
  "x": 100, "y": 100, "width": 130, "height": 120,
  "roundness": { "type": 2 }
}

Use diamonds for: decision points, VIPs, load balancers, routing elements.

Text

{
  "type": "text",
  "x": 110, "y": 110, "width": 180, "height": 25,
  "text": "Label text here",
  "fontSize": 14,
  "fontFamily": 2,
  "textAlign": "center",
  "verticalAlign": "top",
  "containerId": null,
  "originalText": "Label text here",
  "autoResize": true,
  "lineHeight": 1.25,
  "roundness": null
}

Text is always a separate element positioned over its parent shape. It is NOT contained inside the shape — you must manually position it.

Multi-line text uses \n:

"text": "Line one\nLine two\nLine three",
"originalText": "Line one\nLine two\nLine three"

Arrow

{
  "type": "arrow",
  "x": 300, "y": 150, "width": 150, "height": 0,
  "points": [[0, 0], [150, 0]],
  "lastCommittedPoint": null,
  "startBinding": { "elementId": "source-id", "focus": 0, "gap": 2 },
  "endBinding": { "elementId": "target-id", "focus": 0, "gap": 2 },
  "startArrowhead": null,
  "endArrowhead": "arrow",
  "roundness": { "type": 2 }
}

Arrow-specific fields:

  • points — array of [dx, dy] offsets from the arrow's x, y origin. First point is always [0, 0].
  • startBinding / endBinding — connect the arrow to elements. The target element MUST list this arrow in its boundElements array. Set to null for unbound (floating) arrows.
    • elementId — the ID of the element to bind to.
    • focus — float from -1 to 1, controls where on the element edge the arrow attaches. 0 = center, negative = toward top/left, positive = toward bottom/right.
    • gap — pixel gap between arrow tip and element edge. Use 2.
  • startArrowheadnull (no arrowhead) or "arrow" (arrowhead at start).
  • endArrowhead"arrow" (standard arrowhead) or null.
  • For bi-directional arrows: set both startArrowhead: "arrow" and endArrowhead: "arrow".

Important: When an arrow binds to an element, you MUST also add the arrow to that element's boundElements array:

// On the target rectangle:
"boundElements": [
  { "id": "arrow-stn-vector", "type": "arrow" }
]

Style Conventions

Roughness

Always use roughness: 0 for professional diagrams. This produces clean, sharp lines.

Value Effect
0 Clean/sharp lines (professional)
1 Slightly rough (default Excalidraw hand-drawn look)
2 Very rough/sketchy

Font Families

The fontFamily integer maps to fonts in the rendering engine:

Value Font Use for
1 Virgil (hand-drawn) Never use for professional diagrams
2 Helvetica All text labels, titles, descriptions
3 Cascadia (monospace) Code-like text: IPs, ports, paths, commands, protocols
4 Excalifont Avoid — renders as Virgil in excalirender

Rule: Use fontFamily: 2 (Helvetica) for everything except code/technical values which use fontFamily: 3 (Cascadia).

Font Size Hierarchy

Role fontSize Example
Group/server title 18 "srv01 (master)", "AWS Cloud"
Section title 16 "Windows Stations (50+)"
Service box label 14 "Vector Aggregator", "Samba Server"
Detail text / sublabel 13 "Jenkins Agent", "Keepalived"
Description / muted text 12 "Each station runs:", "→ Loki (logs)"
Arrow labels 11 "mTLS gRPC", "HTTPS", "Vector :9000/9001"
Small annotations 10 Compact arrow labels when space is tight

Stroke Width Hierarchy

Role strokeWidth Usage
Group containers 2 Outer boundary of logical groups (servers, cloud, stations)
Service boxes 1 Individual service rectangles inside groups
Primary arrows 2 Main data flow arrows
Secondary arrows 1 Internal/standby/dashed arrows

Stroke Styles

Value Usage
"solid" Default for all active/primary elements
"dashed" Standby/backup elements, inactive paths, internal flows

Opacity

Value Usage
100 Default for all active elements
60 Standby/secondary arrows and their labels

Color Palette

Use a consistent semantic color scheme. Each color has a stroke (saturated) and background (pastel) pair.

Semantic Colors

Semantic Role strokeColor backgroundColor Usage
Neutral / container #1e1e1e #e9ecef Generic groups, station containers
Active / healthy #2f9e44 #ebfbee Active servers, healthy state
Error / danger #e03131 #fff5f5 / #ffe3e3 Idle/failed servers, SPOF callouts, critical sync
Primary service (blue) #1971c2 #d0ebff / #e7f5ff Samba, SMB, station items
Telemetry (orange) #e8590c #fff4e6 Vector, monitoring, metrics
Storage (purple) #6741d9 #f3f0ff rclone, S3, storage sync
Routing / VIP (amber) #f08c00 #fff3bf Keepalived, VIP, load balancers
Utility / secondary #868e96 #f8f9fa Jenkins, IAM, non-critical services
Cloud #1971c2 #e7f5ff Cloud provider groups (AWS, GCP)

Text Colors

Role strokeColor
Group title text Same as group's strokeColor
Service label text Same as service box's strokeColor
Muted/description text #868e96
Arrow label text Same as arrow's strokeColor

Background Colors for Text

Text elements should always have "backgroundColor": "transparent".


Layout Guidelines

Spacing

Between Gap (px)
Group container edge → inner elements 20-25
Inner elements (vertical stacking) 8-14
Major groups (horizontal) 60-100
Major groups (vertical) 40-60
Arrow label → arrow line 8-12 above the line

Alignment

  • Group titles: Centered horizontally within the group, 10-12px from top edge.
  • Service boxes: Left-aligned within group with equal left/right padding (20-25px).
  • Service labels: Centered within their service box.
  • Arrow labels: Positioned above or beside the arrow, using the same color as the arrow.

Canvas Sizing

  • Typical diagram width: 1000-1200px
  • Typical diagram height: 500-650px
  • Leave 30-40px margin on all sides from the outermost elements

Element Naming Convention

Use descriptive kebab-case IDs:

  • Groups: srv01-group, cloud-group, stations-group
  • Labels: srv01-label, cloud-label
  • Services: srv01-vector, srv01-samba
  • Service labels: srv01-vector-label
  • Arrows: arrow-stn-vector, arrow-srv01-cloud
  • Arrow labels: arrow-stn-vector-label

Patterns

Container with Services Pattern

A server or logical group containing multiple services:

┌─ Group container (strokeWidth: 2, semantic color) ──────┐
│  Title (fontSize: 18, same strokeColor as group)         │
│  Subtitle/IP (fontSize: 12, fontFamily: 3, gray)         │
│                                                          │
│  ┌─ Service A (strokeWidth: 1, service color) ────────┐  │
│  │  Service A Label (fontSize: 14)                     │  │
│  └─────────────────────────────────────────────────────┘  │
│  ┌─ Service B ─────────────────────────────────────────┐  │
│  │  Service B Label                                    │  │
│  └─────────────────────────────────────────────────────┘  │
│  ┌─ Small A ──────┐  ┌─ Small B ──────┐                  │
│  │  (fontSize: 13) │  │  (fontSize: 13) │                  │
│  └────────────────┘  └────────────────┘                  │
└──────────────────────────────────────────────────────────┘

Arrow with Label Pattern

Arrows always have a separate text element as their label:

// Arrow element
{ "id": "arrow-a-to-b", "type": "arrow", ... },
// Label element (positioned above the arrow midpoint)
{ "id": "arrow-a-to-b-label", "type": "text", "fontSize": 11, "fontFamily": 3, ... }

Active/Standby Server Pair

  • Active server: solid border, strokeColor: "#2f9e44", backgroundColor: "#ebfbee"
  • Standby server: dashed border (strokeStyle: "dashed"), same colors but dashed
  • Bisync arrow between them: strokeColor: "#e03131", bi-directional arrowheads

VIP / Load Balancer

Use a diamond shape with amber coloring:

{
  "type": "diamond",
  "strokeColor": "#f08c00",
  "backgroundColor": "#fff3bf",
  "strokeWidth": 2
}

SVG/PNG Generation

Using excalirender (Docker)

The recommended tool for converting .excalidraw files to SVG/PNG is excalirender via Docker:

# Single file to SVG
docker run --rm --platform linux/amd64 \
  -v "$(pwd)/diagrams:/data" -w /data \
  jonarc06/excalirender:latest \
  diagram.excalidraw --format svg -o diagram.svg

# Single file to PNG
docker run --rm --platform linux/amd64 \
  -v "$(pwd)/diagrams:/data" -w /data \
  jonarc06/excalirender:latest \
  diagram.excalidraw --format png -o diagram.png

# With scale factor (for higher resolution PNG)
docker run --rm --platform linux/amd64 \
  -v "$(pwd)/diagrams:/data" -w /data \
  jonarc06/excalirender:latest \
  diagram.excalidraw --format png --scale 2 -o diagram@2x.png

# Recursive (all .excalidraw files in directory)
docker run --rm --platform linux/amd64 \
  -v "$(pwd)/diagrams:/data" -w /data \
  jonarc06/excalirender:latest \
  --recursive --format svg

Notes:

  • --platform linux/amd64 is required on ARM hosts (macOS Apple Silicon, AWS Graviton). The image is x86_64 only and runs via QEMU emulation (~1 second per diagram).
  • Output SVGs include embedded fonts (Helvetica, Cascadia) so they render correctly without network access.
  • The Docker image is jonarc06/excalirender:latest (~50MB).

Makefile Target

Standard Makefile target for batch conversion:

EXCALIRENDER_IMAGE := jonarc06/excalirender:latest
DIAGRAMS_SRC := docs/assets/diagrams
DIAGRAMS_OUT := docs/assets/images

diagrams: ## Convert Excalidraw diagrams to SVG
	@echo "Converting Excalidraw diagrams to SVG..."
	@for f in $(DIAGRAMS_SRC)/*.excalidraw; do \
		name=$$(basename "$$f" .excalidraw); \
		echo "  $$name.excalidraw -> $$name.svg"; \
		docker run --rm --platform linux/amd64 \
			-v "$$(pwd)/$(DIAGRAMS_SRC):/data" -w /data \
			$(EXCALIRENDER_IMAGE) \
			"$$name.excalidraw" --format svg -o "/data/$$name.svg"; \
		mv "$(DIAGRAMS_SRC)/$$name.svg" "$(DIAGRAMS_OUT)/$$name.svg"; \
	done
	@echo "Diagrams converted"

Alternative Tools

Tool Install Pros Cons
excalirender (Docker) docker pull jonarc06/excalirender Best rendering quality, embedded fonts, SVG+PNG+PDF, no native deps x86_64 only (needs QEMU on ARM), requires Docker
excalidraw-to-svg (npm) npm install excalidraw-to-svg Pure Node.js, no native deps Requires canvas npm package for some elements; broken with recent Node versions
excalidraw_export (npm) npm install -g excalidraw_export Has --embed-fonts option Requires native canvas (Cairo/Pango); build issues on ARM

Referencing in MkDocs Material

<figure markdown="span">
  <img src="../../assets/images/diagram-name.svg" alt="Description" style="width: 100%; max-width: 1200px;">
  <figcaption>Caption text (revision YYYY-MM-DD)</figcaption>
</figure>

Checklist for New Diagrams

Before committing a new .excalidraw file, verify:

  • All elements have roughness: 0
  • Text uses fontFamily: 2 (Helvetica) or fontFamily: 3 (Cascadia for code)
  • Font sizes follow the hierarchy (18 → 16 → 14 → 13 → 12 → 11)
  • Colors follow the semantic palette (not arbitrary colors)
  • Group containers use strokeWidth: 2, inner elements use strokeWidth: 1
  • All arrows have proper startBinding/endBinding with matching boundElements on targets
  • Arrow labels are positioned above/beside the arrow with matching color
  • Element IDs are descriptive kebab-case
  • Each seed and versionNonce is unique
  • text and originalText fields match on all text elements
  • Standby/inactive elements use strokeStyle: "dashed" and/or reduced opacity: 60
  • SVG has been regenerated via make diagrams or docker run excalirender
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment