Diagrams
Boxes, and the wires between them. This is the default family — nothing to declare — and the one every other page borrows its nodes and links from.
A row, or a column
{ direction: column; gap: 35; }
|oval#visitor| "Visitor"
|box#api| "API"
|hex#worker| "Worker"
|cyl#db| "Postgres"
visitor -> api -> worker -> db
Every container is a flow until told otherwise. direction: row reads left to
right, column top to bottom, and gap: is the space between children.
That one word is the whole of it. Change column to row and the same four
nodes read across the page instead of down it.
The shapes
{
layout: grid; columns: repeat(5); gap: 26; padding: 24;
|tile::column| { gap: 9; align: center; }
|cap::block| { font-size: 12; color: --muted; }
}
|tile| [ |block| "block" { padding: 12 16; fill: --rose-wash; stroke: --rose-deep; }; |cap| "block" ]
|tile| [ |box| { width: 62; height: 42; fill: --rose-wash; stroke: --rose-deep; }; |cap| "box" ]
|tile| [ |rect| { width: 62; height: 42; fill: --orange-wash; stroke: --orange-deep; }; |cap| "rect" ]
|tile| [ |oval| { width: 64; height: 46; fill: --amber-wash; stroke: --amber-deep; }; |cap| "oval" ]
|tile| [ |hex| { width: 64; height: 46; fill: --lime-wash; stroke: --lime-deep; }; |cap| "hex" ]
|tile| [ |slant| { width: 64; height: 42; fill: --green-wash; stroke: --green-deep; }; |cap| "slant" ]
|tile| [ |cyl| { width: 56; height: 46; fill: --teal-wash; stroke: --teal-deep; }; |cap| "cyl" ]
|tile| [ |diamond| { width: 62; height: 46; fill: --sky-wash; stroke: --sky-deep; }; |cap| "diamond" ]
|tile| [ |poly| { points: 0 -26, 26 -6, 15 24, -15 24, -26 -6; fill: --blue-wash; stroke: --blue-deep; }; |cap| "poly" ]
|tile| [ |line| { points: -30 0, 30 0; marker-end: arrow; stroke: --purple-deep; stroke-width: 3; }; |cap| "line" ]
|tile| [ |path| { path: "M -30 24 Q 0 -26 30 24"; stroke: --purple-deep; stroke-width: 4; fill: none; }; |cap| "path" ]
|tile| [ |icon| { symbol: house; width: 34; height: 34; stroke: --red-deep; fill: none; }; |cap| "icon" ]
|tile| [ |sketch| { draw: move(0, 0) right(56):a down(34) left(56) close(); fill: --rose-wash; stroke: --rose-deep; }; |cap| "sketch" ]
|tile| [ |box| "badge" { width: 62; height: 42; fill: --gray-wash; stroke: --gray-deep; font-size: 11; } [ |badge| "3" ]; |cap| "+ badge" ]
Those are the primitives. Every template — |group|, |table|, |badge|,
|mindmap|, and the rest — is a bundle of defaults over one of them, so
anything you can do to a |box| you can do to a |group|.
A group frames its children
{
gap: 60;
|group| { direction: column; gap: 16; }
.client { fill: --rose-wash; stroke: --rose-deep; color: --rose-ink; }
.backend { fill: --purple-wash; stroke: --purple-deep; color: --purple-ink; }
}
|group#browser| "Browser" [
|box#ui| "UI" .client
|box#cache| "Cache" .client
]
|group#server| "Server" [
|box#api| "API" .backend
|cyl#db| "Postgres" .backend
]
browser.ui -> server.api "fetch"
server.api -> server.db
server.api --> browser.cache "store"
A |group| draws a frame around a region and lifts its label to a caption
above the corner.
Links written inside a group’s own [ ] name that group’s children bare;
written at the root, as these are, they need the path.
A grid, with cells and spans
{ layout: grid; columns: 120, 120, 120; gap: 16; align: stretch; justify: stretch; }
|box#head| "Header" { span: 3; fill: --sky-wash; stroke: --sky-deep; }
|box#nav| "Nav" { fill: --amber-wash; stroke: --amber-deep; }
|box#main| "Main" { span: 2 2; fill: --teal-wash; stroke: --teal-deep; }
|box#side| "Side" { fill: --amber-wash; stroke: --amber-deep; }
|box#foot| "Footer" { cell: 1 4; span: 3; fill: --sky-wash; stroke: --sky-deep; }
layout: grid needs columns: — fixed sizes, auto, or repeat(N) — and
fills left to right, wrapping at the count.
span: cols rowscovers several tracks.cell: col rowplaces one child exactly; the rest flow around it.- Tracks are floors, not ceilings: a track grows to its widest child, so a grid never clips.
align and justify set to stretch are what fill each cell with its child —
without them the spans would be invisible.
Out of the flow: pin and translate
{ gap: 40; }
|box#card| "Card" { width: 160; height: 90; fill: --purple-wash; stroke: --purple-deep; } [
|badge| "3"
|block| "draft" { pin: bottom left; translate: 8 -6; color: --muted; font-size: 11; }
]
|box#plain| "Plain" { width: 100; height: 90; translate: 0 20; }
pin: lifts a child out of the flow and seats it on a point of its parent — a
center, an edge, or a corner — as an overlay that never grows the parent. A
|badge| is exactly that: a pinned block with the offset already set.
translate: x y nudges any node after it has been placed, without moving its
siblings. So pin: center plus a translate: is an exact coordinate with no
arithmetic.
Alignment and stretch
{
gap: 24;
|box| { fill: --teal-wash; stroke: --teal-deep; color: --teal-ink; }
}
|group#start| "align: start" { direction: column; align: start; width: 140; } [
|box| "short"
|box| "a longer one"
]
|group#stretch| "align: stretch" { direction: column; align: stretch; width: 140; } [
|box| "short"
|box| "a longer one"
]
|group#end| "justify: end" { direction: column; justify: end; height: 160; } [
|box| "low"
]
justify runs along a flow’s axis; align runs across it. Both default to
center.
stretch fills a child to the cross axis, and evenly spreads the main-axis
slack. Neither does anything until the container is bigger than its packed
children — an explicit width: or height:, or a grid’s fixed tracks, is what
makes the slack to spread.
Go deeper
The layout model and flow, grid, stack & tree have the full rules; the box model covers sizing, padding and wrapping.