D2 Diagramming Reference

Declarative diagramming with D2. Shapes, containers, sequence diagrams, and Antora integration via Kroki.

Basics

Shape declarations and connections
server: Web Server
database: PostgreSQL {
  shape: cylinder
}
server -> database: queries

Shapes are declared by name. Braces add properties. Arrows (->, , <→) create connections.

Connection labels and styling
client -> lb: HTTPS {
  style.stroke-dash: 3
}
lb -> app1: HTTP
lb -> app2: HTTP
app1 -> db
app2 -> db

Shapes

Shape types — rectangle, cylinder, circle, diamond, and more
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)

Nested containers for logical grouping
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

Layout engine selection via CLI
d2 --layout=elk diagram.d2 output.svg     # Hierarchical (default)
d2 --layout=dagre diagram.d2 output.svg   # Directed acyclic graph
Grid layout for structured placement
grid: {
  grid-rows: 2
  grid-columns: 3
  cell1
  cell2
  cell3
  cell4
  cell5
  cell6
}

Styling with Catppuccin Mocha

Catppuccin Mocha palette for consistent theming
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

Sequence diagram with actors and messages
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

Icon from a URL
k8s: Kubernetes {
  icon: https://icons.terrastruct.com/tech/kubernetes.svg
}

CLI Commands

Render to SVG and PNG
d2 diagram.d2 diagram.svg
d2 --format png diagram.d2 diagram.png
Watch mode — re-renders on file change
d2 --watch diagram.d2 diagram.svg
# Opens browser with live reload
Dark theme rendering
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.

Prototype a diagram inline
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.

Watch mode — live reload as you edit the temp file
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
Promote prototype to project
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
Batch render a category
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 diagram block in AsciiDoc via 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)

Define once, apply everywhere — change one place, all nodes update
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
}
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

Arrange children in horizontal rows
vlans: VLAN Segmentation {
  grid-columns: 4
  v100: "VLAN 100\nManagement"
  v10: "VLAN 10\nData"
  v40: "VLAN 40\nResearch"
  v50: "VLAN 50\nIoT"
}

Verification

Batch render with pass/fail reporting
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
Render directly from source
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

--dark-theme 200

Manual colors

Hand-drawn mode

--sketch

FAILne

Watch/live reload

--watch

FAILne

Edge routing

Decent

splines=ortho

Use D2 for architecture and context. Use Graphviz for wiring precision.

See Also