5. The Box Model

A node’s bounding box is the smallest axis-aligned rectangle containing it, stroke included.

  1. Centre origin. Every bbox is centred at the parent’s origin by default.
  2. Source order = render order; later draws on top, with pinned children above the in-flow ones. layer: N overrides; ties break by source order.
  3. Strokes count toward the bbox — width: 100 height: 50 stroke-width: 4 → 104×54. Only painted strokes: stroke: none paints nothing and counts nothing, whatever stroke-width says — so a bare |block| (which keeps stroke-width: 2 invisibly, SPEC 7) truly sizes to its content.
  4. |path| takes native top-left coordinates rather than a centred bbox. (A node’s origin may also sit off its bbox centre — a |sketch|’s pen origin, a pattern:’s seed, a |drawing|’s datum — SPEC 12, SPEC 15.1.)
  5. Rotation applies last as an SVG transform; the rotated bounding rectangle propagates upward.

pin — out of the flow

Every child is in flow by default — laid out by its container’s layout (SPEC 11). pin lifts a child out, aligning the child’s matching point flush with a named point of the parent:

pin:The child sits…
none (default)— in flow; nothing is pinned
centercentre on the parent’s centre
top · bottom · left · rightflush against that parent edge
top left · top right · bottom left · bottom rightwith its corner on that parent corner

The anchor is the parent’s drawn box — border and padding included, and the box it ends up with: a container grown after its interior was laid out (a stretched grid cell, a flex child filling the cross axis) re-seats its overlays on the grown box.

A pinned child is an overlay. It does not grow the parent — a parent of only pinned children collapses to 2 × padding — and it paints above the in-flow children, so a badge needs no explicit layer. The canvas always includes it, so an overlay is never clipped. Set layer: to reorder overlapping pins, or to push one beneath the flow.

translate and rotate — the universal nudge and turn

translate: x y shifts a node by (x, y) after it is placed. It works on every node — flow children, pinned children, text nodes, the root alike — and is layout-neutral: siblings don’t move, the parent doesn’t grow, no size changes. It is CSS’s standalone translate, baked into the node’s origin (so a standalone SVG needs no transform variable); the canvas still includes the shifted node.

There is no numeric coordinate property. Because the parent’s origin is its centre, pin: center + translate: x y lands a child’s centre at parent-local (x, y) — explicit coordinates with no node-size arithmetic.

rotate: N turns a node N degrees about its bbox centre, applied last as an SVG transform. Like translate, it works on any node, text included. pin (which needs a parent anchor and takes a child out of the flow) is a box job; to pin text, wrap it in a |block|.

Auto-sizing

width and height default to auto — the bbox sizes to its content (text or child nodes) plus padding on each side (default 20 on a framed box; there is no separate text padding). Sizing is border-box: padding sits inside the box, never added on top, and the two axes are independent. An explicit width / height is a floor — the box is exactly that size when its content fits, and grows past it (to content + padding) when the content is larger, so a box never clips or spills its content. A box with no in-flow content — empty, or holding only pinned overlays — has nothing to grow for: an explicit size stands exactly as written, and an auto one falls to 2 × padding on each axis (the default padding 20 gives a 40 × 40 minimum).

Padding also places the content. The content area is the box inset by padding, and the content sits within it; symmetric padding centres it, while an asymmetric padding: t r b l offsets it — padding: 4 4 20 4 lifts the content toward the top, away from the larger bottom inset, exactly like CSS.

max-width — wrap to fit

max-width: N caps a box’s auto width; text-wrap: wrap | nowrap (default wrap) says whether text inside breaks into lines to honour the cap — both inert without a finite max-width. Wrapping prefers whitespace and falls back to breaking inside a word (grapheme boundaries), so the no-clip / no-spill law holds at any width. The wrapped size is the measured size — it feeds auto-sizing, grid tracks, gutters, spacing, link labels, and routing obstacles alike. Wrapping is decided once, at measurement, against max-width (or the content’s natural width); a later stretch widens the box, never the line breaks. Three errors keep it honest (SPEC 21): nowrap text that cannot fit the cap, a non-text child wider than the cap (only text wraps), and a width floor above it.

Exceptions: a text node sizes to its glyphs (no padding), widened by letter-spacing and given line-spacing between \n lines; |icon| is a square that grows with its [ ] text (a 32 floor) and needs a symbol; |line| / |poly| / |image| / |path| require their geometry (points / src / path) and error without it. |block| carries padding: 0, so a bare block sizes to its content exactly.

Text is measured from real metrics. Width = Σ per-glyph advances at the compile-resolved font-weight, read from the bundled metrics tables (SPEC 6); no kerning or shaping (≈ 1 % of a line, the documented tolerance — SPEC 24). Metrics follow the kind, not the name: a mono font-family — a known-mono name, or any name containing “mono” — measures on the mono table (exactly 0.6 em per glyph, at every weight), every other family, the bundled default included, on the proportional table. An unknown glyph falls back to a fixed advance (wide for the CJK ranges). Vertical centring is cap-height optical centring, from the same tables.