Skip to content
Components

Table of contents

The “On this page” rail: the headings of one document, on a left rule, with the one you are reading marked. It tracks scroll position rather than the URL hash, because those two disagree the moment anybody scrolls.

Anatomy

Pointed at this page's real heading ids, so every link resolves. The rail on the right of this page is another one, built by the docs chassis from the sections it already renders.
Code
<GdPageToc
  :items="[
    { id: 'anatomy', label: 'Anatomy' },
    { id: 'api', label: 'API' }
  ]"
/>

11.5px items on a 2px left rule, the active one in accent. It is --gd-size-toc wide and carries no heading of its own — the chassis that places it supplies the “On this page” label, because a rail that titles itself cannot be reused anywhere the title is wrong.

How active is decided

  • From SCROLL POSITION, never location.hash. The hash only changes when somebody clicks a link, so scrolling past four headings leaves it pointing at the first — and the rail then says you are somewhere you are not.
  • Computed, not observed. An IntersectionObserver needs a detection band, and a band tight enough to pick exactly one heading is a band a short page can fall entirely outside. That is not hypothetical: it froze the rail on whichever heading last crossed it.
  • The rule is “the last heading whose top is above the reading line”. Unambiguous at every scroll position, including the two that break observers — the very top and the very bottom.
  • It listens on the nearest SCROLLING ancestor, not the window. Inside an app frame the document does not scroll at all, so a window listener would never fire.
  • Reads are throttled to a frame and the listener is passive, so it never fights the scroll it is measuring.

Never declare it twice

The items are DATA, and the temptation is to hand-write them beside the sections they describe. Do not: two lists with nothing holding them together drift the first time anybody edits a heading, and the failure is silent — a contents rail with a stale label or a dead anchor looks exactly like a working one.

The docs chassis reads them off the sections it is already rendering, so the rail cannot disagree with the page. Where a caller builds its own list, derive it from whatever already knows the headings rather than typing it out.

API

Props

PropTypeDefault
items{ id: string; label: string }[]—

The headings, in document order. Each `id` must match an element on the page — the rail scrolls to it and reads its position back. One list, not two: the docs chassis derives these from the sections it already renders rather than declaring them a second time.

For the other rail — the destination nav down the left of an app or a docs site — see Side navigation.

Navigate

Esc