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
Visitor API Worker Postgres

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

block block box rect oval hex slant cyl diamond poly line path icon sketch badge 3 + badge
{
  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"
UI Cache Browser API Postgres Server fetch 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; }
Header Nav Main Side Footer

layout: grid needs columns: — fixed sizes, auto, or repeat(N) — and fills left to right, wrapping at the count.

  • span: cols rows covers several tracks.
  • cell: col row places 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; }
Card 3 draft Plain

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"
]
short a longer one align: start short a longer one align: stretch low justify: end

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.