Skip to main content

Table of Contents

3 min readStableBeginner

The table of contents gives every page on-page navigation built from its H2 and H3 headings, with a scroll-spy highlight for the section in view. On wide screens it's a sticky sidebar; below 1280px the same entries collapse into an "On this page" panel under the article.

Quick Start

Headings pick up id attributes from rehype-slug, so the list builds itself with no config and no authoring.

MDX
MDX
## Section one
 
## Section two

Those two headings are the minimum. The component reads them straight out of the rendered DOM with document.querySelectorAll('h2[id], h3[id]'), so an H1, an H4, or a heading that somehow lost its id never shows up.

Adding Options

Nest H3 headings under an H2 and the list indents them one level, so the reader sees the page shape instead of a flat run of links.

MDX
MDX
## Installation
 
### Requirements
 
### Install the CLI
 
## Configuration

Those five headings become five entries, with Requirements and Install the CLI indented under Installation. Long heading text is clamped to a single line rather than wrapping. Drop the page to one heading and the table of contents disappears on both layouts — it needs at least two.

Advanced

Scroll this page and watch the highlight move in the sidebar. The desktop list tracks the section in view with an IntersectionObserver set to a -80px 0px -60% 0px root margin and a 0 threshold, so a heading goes active once it clears the sticky header and sits in the top 40% of the viewport. When several headings qualify at once, the first one in the observer's batch wins.

MDX
MDX
## Overview
 
## Endpoints
 
### Create a session
 
### Refresh a session
 
## Errors

The two layouts differ in more than width:

  • Desktop sits in a 224px column, sticks 40px from the top, and scrolls independently once the list outgrows the viewport. The active entry gets a brand-colored left border and brand text against the rail.
  • Below 1280px the same five entries move into a collapsible panel whose badge reads 5 sections, counting H2 and H3 together. It starts collapsed, the chevron rotates a quarter turn when it opens, and the toggle carries aria-expanded plus aria-controls pointing at the panel body.
  • Tapping a link on mobile jumps to the section and closes the panel. There's no IntersectionObserver on that layout, so nothing highlights as you scroll.
  • Both layouts label themselves "On this page" for screen readers, and every route change rebuilds the list from the new page and collapses the panel again.

Options

isMobilebooleanDefault: false

Renders the collapsible panel instead of the sticky sidebar, and skips the scroll-spy observer. The doc layout mounts both and lets CSS decide which one is visible.

heading depthstringDefault: H2 + H3

Only h2 and h3 elements that carry an id appear in the list.

minimum headingsnumberDefault: 2

A page with fewer than two qualifying headings renders no table of contents on either layout.

active section marginstringDefault: -80px 0px -60% 0px

The IntersectionObserver root margin that decides which entry is highlighted. Desktop only.

layout breakpointstringDefault: 1280px

Tailwind's xl. At and above it the sticky sidebar renders; below it the collapsible panel takes over.

entry textstring

Taken from each heading's text content, with a leading # trimmed off so the anchor marker never leaks into the list.

Was this page helpful?