5. The Box Model
A node’s bounding box is the smallest axis-aligned rectangle containing it, stroke included.
- Centre origin. Every bbox is centred at the parent’s origin by default.
- Source order = render order; later draws on top, with pinned children above
the in-flow ones.
layer: Noverrides; ties break by source order. - Strokes count toward the bbox —
width: 100 height: 50 stroke-width: 4→ 104×54. Only painted strokes:stroke: nonepaints nothing and counts nothing, whateverstroke-widthsays — so a bare|block|(which keepsstroke-width: 2invisibly, SPEC 7) truly sizes to its content. |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, apattern:’s seed, a|drawing|’s datum — SPEC 12, SPEC 15.1.)- 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 |
center | centre on the parent’s centre |
top · bottom · left · right | flush against that parent edge |
top left · top right · bottom left · bottom right | with 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.