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.

Scroll.Pinned · sticky region (Toolbar / page header)

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.)

Canvas.Orientation · identity strip
Canvas.Body · pan / zoom surface

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>
div

The vertical-document container. Max-width 980px, centered, padded for readable prose.

<Scroll.Pinned>
div

Sticky 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>
div

Explicit semantic grouping for the flowed body. Optional — children of <Scroll> can sit directly inside without it.

<Canvas>
div

The 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>
Card

Slim 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>
Card

The 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).