Templates
A Template answers exactly one question — how do you move through the content well — and there are only two answers (ADR-0028). Scroll: content flows top to bottom and scrolls inside the well while the shell stays put. Canvas: a bounded surface you pan and zoom; the content doesn't flow. That's everything a Template owns. Everything you drop inside — a header, a toolbar, a collection, prose, controls — is a Pattern. Quick test: if a piece is conditional, it's a Pattern, not a Template.
Scroll is shipped as <Scroll> in @halo-compliance/ui; Canvas ships right beside it, first put to work by the engineering runbook's architecture graph. Scroll does the heavy lifting on this site — every doc page you're reading runs in one; Canvas is the runbook's.
You are inside one
This page — like every doc page on the design site — is rendered inside a <Scroll> mounted in the content well (ContentWell), which is mounted in the site's <SiteLayout variant="application">. The AppBar and the corner controls stay put while this content scrolls underneath — that's the Scroll promise.
Scroll
The vertical document. You get a centered, readable column (max ≈ 980px so prose doesn't sprawl), comfortable vertical padding, and a pin slot for sticky regions. The shell never scrolls — only Scroll does, inside its well.
flowed body
Most routes just drop their content inside <Scroll>. Padding, max-width, and centering are all handled for you.
with a pinned region
Use <Scroll.Pinned> to pin a Pattern (a Toolbar, a page header) to the top of the well's scroll context. Stack as many Pinned children as you need — they keep document order.
Canvas
Fill-the-well — Scroll's spatial sibling. Instead of flowing and scrolling, the well holds one bounded surface you pan and zoom; the page never scrolls, the surface does the navigating. Two Card slots: a slim Orientation strip (identity only) and a Body that takes the rest of the height and clips the surface edge to edge. First shipped for the engineering runbook's architecture graph — but not every canvas is a graph: a board, a media viewer, or a map is just as happy here, and the legend and controls belong to the surface, not the template. (The gold-frame, touch-the-splash treatment belongs to the proposed system; here the slots are flat, solid Cards.)
orientation strip
A slim top strip — identity only (eyebrow, title, lede) — kept thin so the body gets the room. <Canvas.Orientation> carries no legend or controls; those live on the surface in the body, because not every canvas is a graph.
body surface
The big one. <Canvas.Body> clips the surface edge to edge, then flex-grows to fill whatever height is left. Whatever you put inside (a React Flow graph, a media viewer, a map) renders at 100% and owns its own pan, zoom, and chrome. The page itself never scrolls. (In the proposed system the gold border doubles as a frame; here the Card is a flat, solid surface with a soft shadow — same job, friendlier outfit.)
Compositional parts
- <Scroll>
divThe vertical-document container. Max-width 980px, centered, padded for readable prose.
- <Scroll.Pinned>
divSticky region anchored to the top of the well's scroll context. For Toolbar, Orientation/page header, or any Pattern that must remain visible during scroll.
- <Scroll.Body>
divExplicit semantic grouping for the flowed body. Optional — children of <Scroll> can sit directly inside without it.
- <Canvas>
divThe fill-the-well container. Flex column at full height, no max-width — the canvas wants the whole well, and the page never scrolls.
- <Canvas.Orientation>
CardSlim top strip Card. Identity only (eyebrow / title / lede), kept thin so the body gets the space. Legends and controls live on the surface, not here.
- <Canvas.Body>
CardThe dominant Card. Clips the surface edge to edge and fills the remaining height; the surface inside owns its own pan / zoom.
The conditionality test (discipline)
Per ADR-0028, the Template owns only what is never conditional. The moment a region turns conditional, either the condition is telling two Templates apart, or the region belongs to a more specific layer (a Pattern). That's why headers, toolbars, and page scaffolds (Detail, Dashboard, Form, Settings, Article, Landing) all moved out of Template and into Pattern — they're conditional. A Template is just how you get around.
Anti-patterns
- Don't put a fixed header inside Scroll. Use <Scroll.Pinned> — it pins to the well's scroll context. A header that isn't pinned just scrolls away with the body, which is no good for orientation content.
- Don't override Scroll's max-width without a reason. The 980px column is sized for comfortable reading at the standard body size. Wider hurts line length; narrower wastes the well. If a layout genuinely needs full width (a wide Collection, a hefty Form), that's a Pattern call: the Pattern overflows the Scroll column with negative margins — Scroll itself keeps its shape.
- Don't introduce a third Template. Split views are Scroll composed in panes; wizards are a Form variant; chats live in the ambient overlay layer. None of those need a new Template (ADR-0028).