Contents

Level 2, declared on 2026-08-08 and unmeasured until that day. A document's own contents beside it: 4 rails on 4 painted screens, 47 rows in total, read from the rendered DOM on 2026-08-19. This line said "1 rail and 14 rows on 1 painted screen", which was true on 2026-08-08, when terms.html was the only long document in the tree, and stayed typed while privacy, cookies and responsible-betting landed on 2026-08-18. The 47 are not divided evenly and no document holds a quarter of them, so a single row number is a fact about one document rather than about the component: the badge carries the total and the section below carries one document's list. Same geometry as the category rail and the opposite job, and it is a rail on a desktop and a disclosure on a phone. Measured in a browser at 390 and 1280, in both pointer branches.

4 rails, 4 screens 47 rows in total 214px, pinned 36 to 44 with a finger

The contents rail, whole

The head, the numbered list and the depth levels. 13 elements against 45.

Why it is a component and not a variant of the category rail

The two rails have the same geometry and opposite jobs. A category rail navigates BETWEEN pages and its rows are buttons; a contents rail navigates INSIDE one page and its rows must be anchors, because a contents built out of buttons is not crawlable and this product's own SEO layer requires every heading anchor to be a real link.

Putting the sections of a legal document into a component called catnav would also have been a naming lie on a screen that already has a count of them. Two things that look the same and mean different things are two components, which is the same test that keeps the consent screen's page placeholder out of skeleton.

It was unmeasured until 2026-08-08, like account (walked and then deleted, action bar) and cookie consent, because it stands on no anchor screen. Walked that day: the one document in the tree held 14 anchors of its own, which is the level formula's blind spot, so the declaration is written in words. HOLDS its own rows, level 2.

Five documents carry a reading column and four carry a contents, and the fifth is a decision rather than a gap. about.html has no rail, and ia/docs/blocks.md is where that was settled before the page was drawn: the bank gives About the STATEMENT profile, and B5, the anchored contents, is a DOC block, which is the four legal documents and not About. A zero has to say which zero it is, and this one is a page the bank never asked to carry the component rather than a page that lost it or a face nobody measured. So the denominator here is four of five, and the fifth is written down.

The rail

at 390at 1280
the rail293x598, in the flow, above the document214 wide, pinned beside it. The height is one document's list
the head293x44, a summary you tap0x0, display:none
a row285x36, and 285x52 where the label wraps206x36, and four rows at 52 because 206 is narrower
a row with a finger44, from the one floor in base.css

The 214 and the sticky are unchanged and what stands beside them is not, 2026-08-19. The two heights in this table were read on 2026-08-08 off terms.html, the only long document in the tree that day, and a rail is exactly as tall as the list it holds, so 598 and 601 are a fact about that document and the cell says so rather than printing a number about the component. What moved is the other column. .read-col carries the measure itself now, at --text-16, which computes 503px from DESK 640 up, against a 600px column with a second cap on the prose inside it before; and .cat-layout:has(.read-col) shrink-wraps the plate to what it holds, so at 1440 terms.html is a 795px plate, the 214 rail plus a 20px gap plus the 503 column plus the plate's own inset, where it was 1140 with 153px of nothing to the right of the text. The rail did not move; the page stopped being wider than its own contents.

Every row carries its number in mono on the right, and the current row takes a brass edge and brass ink, both of them keyed to aria-current rather than to a class, so the drawing and the accessibility tree cannot disagree about which section you are in.

Mobile is a disclosure, and the reason is measured

The IA said the contents is collapsed above the body on mobile. It was built OPEN first, to check whether that was caution rather than a finding.

At 360 the fourteen rows pushed the document's H1 entirely below the fold, so the first screen of a legal page was a list of links to a title the reader had not seen. The IA was right, and the check is what makes it a decision instead of a rule somebody followed.

Desktop does not need the control at all, so the summary is removed above 900 and the content is forced open through the disclosure's own content pseudo-element, which is the only way an author can override the closed state the browser owns.

The sticky top is 66px and not the 120px the other two rails use

120px is the header plus the condensed category strip plus 7px of air, and the strip is revealed by an observer watching the feed's own category bar. A document page has no category bar, so the observer returns before it starts and the strip is never once shown: measured at scroll 1200 on this screen, the condensed strip is 0px tall and the header never takes its scrolled class.

The rail was clearing 61px of chrome that does not exist, and the visible cost was at the other end: this layout puts the rail's natural top at 96px, ABOVE the 120px threshold, so sticky clamped it on the first frame and the contents began 24px below the H1 beside it. 66px is the header and the same 7px of air the feed rail already uses.

And it is capped, because a pinned box taller than the window hides its own tail forever. At 601px and pinned at 120px this rail needed 721px of window: on a 1366x768 laptop rows 12, 13 and 14 were off the bottom and stayed there for the first 3,500px of a 4,884px document. The cap is written in small-viewport units with a viewport-unit fallback under it, because the tall viewport is the wrong one: a browser that hides its chrome on scroll would put the foot of the rail back under the fold.

It is the same defect and the same afternoon as the sub-category rail in category nav. Two components, one shape, found by scrolling rather than by reading.

The rail slot is a contract

A rail declares its own flex-basis and its own sticky, exactly as the sub-category rail does, and the shell declares only that there are two columns. So a third rail can arrive later without either file learning about it.

The 214px is the same 214px the category rail uses, and that is deliberate: the slot is one slot, and a document that put its contents at a different width would make the two rails disagree about what a rail is.

What it does with width

Its own width query: 900 RAIL. At RAIL the contents become a flex:0 0 214px sticky column with its own scroll, the same 214 and the same mechanism as the sub-category rail. Two components arriving at one rung with one shape is what makes RAIL a rung and not a coincidence.

The rule

Inside a page is an anchor; between pages is a button. Same geometry, opposite jobs, two components.

A pinned box is capped to the window, in small-viewport units. Otherwise its tail is unreachable and scrolling cannot help.

The current row is keyed to the attribute. A class could say one thing while the accessibility tree said another.

The anti-rule

Never let a contents rail push the title off the first screen. Fourteen rows at 360 did exactly that, and the measurement is why the disclosure exists.

Never copy a sticky offset between screens. The 120px the feed rails use is header plus a strip that this screen never shows, and the rail cleared 61px of nothing while starting 24px below the heading beside it.

Never call two jobs one component because the geometry matches. A contents made of buttons is not crawlable, and this product's own SEO layer requires the opposite.

Where the rest of it is

The level and its reason: the molecules page. The stylesheet, whose comments are the argument in full: components/toc.css. The other rail: category nav. The shell that hands both a slot: components/patterns/browse-shell.css. The requirement that its rows are links: ia/docs/pages/seo.md. The floor: components/base.css.