../reference/dsl-yaml.md
and ../reference/dsl-d2.md. For the
machine-readable inventory call isotopo capabilities or read
CAPABILITIES.md. For complete worked scenes see
the samples index.
The one rule that matters: never hand-compute coordinates.
Position parts with layout (containers) and place (sibling
relations); all gaps are in cells (1 cell = gridStep, default 40
world units). offset exists only as a fine-tune delta on top of a
solved position.
Positioning
I want B next to A
rightOf/leftOf pins world x,
inFrontOf/behind pins world y (front = toward the viewer). The
unconstrained axis aligns to the referenced sibling — align: start|center|end (default center). References must be SIBLINGS in
the same parts: list.
I want a pipeline / chain of parts
Chainplace along one axis; the solver resolves the chain
topologically:
I want a staircase climbing the iso axis
Each tile is right of AND in front of its predecessor (gap 0 = corner-to-corner):samples/topology/rag-pipeline
for a two-step stair anchoring a full scene (substrates along the
iso diagonal).
I want a grid / dashboard of cells
One container,layout: grid — the substrate auto-sizes:
mode: row marches along world +x, column along +y. See
samples/topology/langchain-app
for 2×2 grid service boards carrying glyph icons.
I want a part stacked ON TOP of another (plinth, ghost volume)
above pins world z — flush on the sibling’s top, centred on its
footprint, no copied heights:
samples/topology/training-compute.
Connectors + elevation:above/wzis for decorative parts with no wiring. If a stacked part needs a connector, the rule below (tiers) applies — keep it coplanar with what it links to.
I want a multi-tier architecture (gateway → service → data)
Group each tier and give the whole tier ONEwz, so connectors
inside a tier stay flat 2D and only tier→tier links cross planes —
that vertical drop then reads as a deliberate cross-layer call:
wz — that
produces a riser with no semantic meaning (the noir-sandbox bug).
I want a hub with satellites around it
One rule —ring: first child is the hub, the rest distribute
clockwise from the back:
samples/topology/ai-platform
— the eight-tile ring is exactly this.
I want different gaps per axis
gapX / gapY override gap independently:
I want to nudge one part a few pixels
Keep theplace/layout, add an offset — it’s applied as a delta
AFTER solving, and parts placed relative to this one follow along:
I want absolute coordinates anyway
offset: {wx, wy, wz} without place is still honored (legacy
documents render byte-identically). Reach for this only for
free-form artistic scatter that relations can’t express.
Containers
I want to wrap N elements in a labeled container
Give the group alayout; never position children by hand:
.d2 (auto-layout does everything):
place against each other instead of a
layout — the substrate still auto-sizes around the solved bbox:
I want a nested container hierarchy (cluster > region > pod)
Nestparts: arbitrarily deep; inner groups are solved first so
outer layouts see their final size:
.d2 — same dot-path semantics:
I want N replicas of one element
count = total layers, gap = world-Z step. Top layer’s id is
pod~2, second from top pod~1, bottom keeps pod. Stacking is
vertical — it composes freely with place/layout on the ground.
I want to lay out a graph automatically
Use.d2 instead of YAML. iso-topology runs dagre by default
(polyline edges) or elk for orthogonal right-angle routing.
Annotations & connectors
I want a labeled callout pinned to a part
Document-levelannotations: block:
top | right | bottom | left. Multi-line via \n.
Default leader-line distance is 60 world units; override with
distance: 90.
I want a directed arrow between two parts
from: api.right-mid, to: db.top-back.
I want grid-aligned (Manhattan) routing
canvas.grid: iso. Use this for
architecture flows; the default straight cuts across the grid.
Golden rule for picking endpoints: connect nodes at the SAME height
(wz/tier). Same-height pairs route as flat, clean 2D L-shapes. When
several nodes could be the source or target, prefer the coplanar one.
Only span heights for a deliberate cross-tier link (e.g. a hovering
control plane projecting down) — that renders a vertical drop segment,
which should be intentional, never an accident of mismatched wz.
I want an async / replication link
Same orthogonal routing as everything else — async is expressed bydash, NOT by a different geometry:
routing: bezier exists for non-iso freeform sketches only — in an
iso scene a curve cuts across the grid and breaks the projection.
I want a dashed connector
dash uses SVG stroke-dasharray syntax: "4 3" = 4 on, 3 off.
Shapes & styling
I want a database / queue / stored-data icon
All three lower to the iso cylinder:I want a user / actor icon
c4-person is also accepted as an alias.
I want a cloud / external system icon
I want a text-only label (no extruded box)
I want an icon on the top face instead of text
The preferred look for showcase scenes — keep top faces visual, push words into screen-space labels or annotations.iso://si/<slug>): ~150 marks vendored from
Simple Icons (CC0) — mysql, postgresql, redis, apachekafka, docker,
kubernetes, terraform, openai, anthropic, pytorch, grafana, … with
the same /light and /RRGGBB color variants. Letter badges
(iso://brand/<name>) remain as fallbacks for non-vendored marks.
Any other icon: value is treated as a URL / data-URI. Full
browsable index with previews: ICONS.md.
I want flat / low / tall extrusion
Adjustgeom.h. Conventions:
For
.d2 input, the height comes from d2ShapeCatalog’s
multiplier × base height. Override per-shape via the YAML path if
you need different heights.
I want brand colors on a specific shape
theme: once and every part
inherits:
../reference/dsl-theme.md for the
full cascade.
I want to reuse one style across many parts
Define a named preset in the theme, reference it by name — no YAML anchors, works in JSON, typos get “did you mean” suggestions:style still overrides individual preset fields. For which
presets a good scene needs, read the
scene design guide.
I want a gradient face / glow / shadow / texture
pattern: {kind: hatch|dots, color, spacing, angle} overlays a
texture on box top faces, cylinder top ellipses, and sphere discs.
Use ONE hero treatment per scene — backglow on everything reads as
noise.
I want line-art / wireframe ghosts
effects.wireframe: true keeps strokes and drops every fill — pair
with a dashed stroke for floating “ghost” frames. None-filled and
wireframe parts are exempt from overlap warnings, so they can sit on
or around solid parts freely:
I want a film-grain / print texture
effects.grain overlays monochrome noise — the editorial “print ad”
register (see duckdb-handdrawn):
I want a screen-horizontal row (editorial layout)
In iso projection a plainrightOf row slopes down-right. To march
straight across the screen, pair rightOf with behind and tune
per-axis gaps so Δx = −Δy:
Canvas / backdrop
I want an iso ground grid
gridStep doubles as the layout/place CELL size — keep them equal
so arranged parts and orthogonal pipes land on the grid texture.
I want dots / hatch / solid instead
I want a transparent / no backdrop
Omitcanvas: entirely.
Common composite patterns
Stat card (icon + value + caption)
Region / zone container
Stacked replicas inside a worker node
Hero-anchored marketing scene
Pick the central element, give it noplace (it anchors the world
origin), chain every satellite off it. See
samples/topology/ai-platform /
clickhouse-hub /
training-compute —
all coordinate-free.
Validation
I want my agent’s output checked before render
PROMPT_TEMPLATE.md for the agent-side
boilerplate.
What the validator checks
Errors (block render):- Unknown shape names, with “did you mean” suggestions
- Connector
from/toreferencing a non-existent part id - Duplicate part ids;
placecycles and dangling references - Negative dimensions or gaps; invalid anchor suffixes
Contrast ratios use WCAG relative luminance (sRGB linearised). Only
#RRGGBB / #RGB colours are evaluated; named colours and
rgba(…) are skipped silently.
I want to track whether an edit improved quality (golden diff)
DiffIssues matches issues by (Path, Message) pair. Use it in an
agent loop to confirm each iteration moves toward zero issues, not
just changes them:
When you want behaviour we don’t have
These are the recurring asks. If you hit one:
For everything else, search this file. If it’s not here and not in
the references, it’s probably a missing primitive.