D2 Diagramming Reference
Declarative diagramming with D2. Shapes, containers, sequence diagrams, and Antora integration via Kroki.
Basics
server: Web Server
database: PostgreSQL {
shape: cylinder
}
server -> database: queries
Shapes are declared by name. Braces add properties. Arrows (->, ←, <→) create connections.
client -> lb: HTTPS {
style.stroke-dash: 3
}
lb -> app1: HTTP
lb -> app2: HTTP
app1 -> db
app2 -> db
Shapes
api: REST API {shape: hexagon}
db: Database {shape: cylinder}
decision: Approve? {shape: diamond}
queue: Message Queue {shape: queue}
cloud: AWS {shape: cloud}
user: Admin {shape: person}
docs: Manual {shape: page}
pkg: Package {shape: package}
Containers (Nesting)
network: Network Layer {
fw: Firewall
lb: Load Balancer
fw -> lb
}
application: App Layer {
api: API Server
worker: Background Worker
}
network.lb -> application.api
Containers group related components. Access nested shapes with dot notation.
Layouts
d2 --layout=elk diagram.d2 output.svg # Hierarchical (default)
d2 --layout=dagre diagram.d2 output.svg # Directed acyclic graph
grid: {
grid-rows: 2
grid-columns: 3
cell1
cell2
cell3
cell4
cell5
cell6
}
Styling with Catppuccin Mocha
vars: {
d2-config: {
theme-id: 200
}
}
server: Web Server {
style: {
fill: "#1e1e2e" # Base
stroke: "#89b4fa" # Blue
font-color: "#cdd6f4" # Text
}
}
database: PostgreSQL {
shape: cylinder
style: {
fill: "#1e1e2e"
stroke: "#a6e3a1" # Green
font-color: "#cdd6f4"
}
}
server -> database: queries {
style: {
stroke: "#f5c2e7" # Pink
}
}
Sequence Diagrams
shape: sequence_diagram
client: Client
server: API Server
db: Database
client -> server: POST /login
server -> db: SELECT user
db -> server: user record
server -> client: 200 OK + JWT
Icons and Images
k8s: Kubernetes {
icon: https://icons.terrastruct.com/tech/kubernetes.svg
}
CLI Commands
d2 diagram.d2 diagram.svg
d2 --format png diagram.d2 diagram.png
d2 --watch diagram.d2 diagram.svg
# Opens browser with live reload
d2 --theme 200 diagram.d2 dark.svg # Dark Mauve theme
d2 --dark-theme 200 diagram.d2 out.svg # Auto dark/light
Quick Render from Terminal
d2 requires a file path — no stdin support. Heredoc to a temp file bridges the gap.
cat << 'EOF' > /tmp/diagram.d2
direction: right
client -> switch: 802.1X
switch -> ise: RADIUS {
style.stroke: "#a6e3a1"
style.animated: true
}
ise -> dc: LDAPS
EOF
d2 --theme=200 /tmp/diagram.d2 /tmp/diagram.svg && xdg-open /tmp/diagram.svg
Single-quoted 'EOF' prevents shell expansion — {braces} and $vars pass through literally to D2. The && chain opens the browser only on successful render.
cat << 'EOF' > /tmp/diagram.d2
a -> b -> c
EOF
d2 --watch --theme=200 /tmp/diagram.d2 /tmp/diagram.svg
# Opens browser — saves to /tmp/diagram.d2 trigger re-render
cp /tmp/diagram.d2 docs/modules/ROOT/examples/diagrams/network/new-diagram.d2
d2 --theme=200 \
docs/modules/ROOT/examples/diagrams/network/new-diagram.d2 \
docs/modules/ROOT/images/diagrams/d2/network/new-diagram.svg
for f in docs/modules/ROOT/examples/diagrams/network/*.d2; do
out="docs/modules/ROOT/images/diagrams/d2/network/$(basename "${f%.d2}.svg")"
d2 --theme=200 "$f" "$out" && printf " → %s\n" "$out"
done
Integration with Antora (Kroki)
[d2,target=network-diagram,format=svg]
....
server -> database: SQL
server -> cache: Redis
....
Kroki renders the D2 source during make build. The diagram appears inline as SVG. Requires Kroki running (handled by make).
Classes (DRY Styling)
classes: {
firewall: {
shape: hexagon
style: {
fill: "#1e3a5f"
stroke: "#89b4fa"
stroke-width: 2
font-color: "#cdd6f4"
}
}
radius-link: {
style: {
stroke: "#f38ba8"
stroke-width: 2
}
}
}
vyos-01: "vyos-01" { class: firewall }
vyos-02: "vyos-02" { class: firewall; style.opacity: 0.6 }
ise -> switch: "RADIUS" { class: radius-link }
class: sets the base. Individual style. properties override selectively.
Markdown Labels
Use triple-pipe |||md … ||| when content contains pipe characters (every markdown table does). Single-pipe |md … | misinterprets table | as the block terminator.
|
ise-01: |||md
### ise-01 (Primary)
| API | Port |
|---|---|
| ERS | :9060 |
| RADIUS | :1812 |
**ISE 3.4** · All personas active
||| {
class: detailed
}
Tooltips and Links (Interactive SVG)
ise-01: "ISE-01" {
tooltip: "Primary ISE — Admin, MnT, PSN, pxGrid"
link: "https://10.50.1.20/admin"
}
Hover shows tooltip, click opens link. Two information layers in one SVG.
Grid Layout
vlans: VLAN Segmentation {
grid-columns: 4
v100: "VLAN 100\nManagement"
v10: "VLAN 10\nData"
v40: "VLAN 40\nResearch"
v50: "VLAN 50\nIoT"
}
Verification
for f in docs/modules/ROOT/examples/diagrams/lab/d2/*.d2; do
out="/tmp/$(basename "${f%.d2}").svg"
if d2 --dark-theme 200 "$f" "$out" 2>&1; then
printf "✅ %-45s → %s\n" "$(basename "$f")" "$out"
else
printf "❌ %-45s FAILED\n" "$(basename "$f")"
fi
done
d2 --dark-theme 200 $(find -name '05-containers.d2') /tmp/containers.svg
D2 vs Graphviz
| Capability | D2 | Graphviz |
|---|---|---|
DRY styling (classes) |
✅ Winner |
❌ Inline only |
Rich node content |
✅ Markdown |
✅ HTML tables |
Port-targeted edges |
FAILne |
✅ Winner |
Interactive SVG |
✅ Tooltips + links |
FAILne |
Dark theme |
|
Manual colors |
Hand-drawn mode |
|
FAILne |
Watch/live reload |
|
FAILne |
Edge routing |
Decent |
✅ |
Use D2 for architecture and context. Use Graphviz for wiring precision.
See Also
-
Kroki — diagram rendering server
-
Diagrams as Code — tool comparison and selection guide