1. Home
  2. ChevronRightLayout
  3. ChevronRightOnThisPage

OnThisPage

A table of contents navigation panel that displays and highlights headers of the current page.

The OnThisPage component renders a floating navigation panel listing headings from the current document, dynamically highlighting active sections as the user scrolls.

It uses an IntersectionObserver under the hood (via AnchorProvider) and a small linear-interpolation trick to glide a highlight indicator between the active anchors. The behavior is automatic for the most common cases — usually you pass headings and you're done.

For 95% of custom layouts, <OnThisPage.Tree headings={headings} /> is enough. Reach for the lower-level pieces only when you want full control over the positioning math.


ImportLink

import { OnThisPage } from 'boltdocs/primitives'

Composable Scroll-Spy ExampleLink

Using the primitive OnThisPage modules, you can assemble a fully custom table of contents. The example below shows how to compose a layout with custom titles, list containers, active indicator lines, and smooth scroll behaviors:

// docs/components/CustomTOC.tsx
import React, { useRef } from 'react'
import { OnThisPage, AnchorProvider, ScrollProvider } from 'boltdocs/primitives'
import { useRoutes } from 'boltdocs/client'

export default function CustomTOC() {
  const { currentRoute } = useRoutes()
  const scrollContainerRef = useRef<HTMLDivElement>(null)

  const headings = currentRoute?.headings || []
  if (headings.length === 0) return null

  // Convert headings database to standard TOC structure
  const tocItems = headings.map((h) => ({
    title: h.text,
    url: `#${h.id}`,
    depth: h.level,
  }))

  return (
    <OnThisPage className="border-l border-subtle pl-6 py-6 bg-transparent">
      {/* 1. Static Title Header */}
      <OnThisPage.Header className="text-xs font-bold uppercase tracking-wider text-zinc-400 mb-4">
        Table of Contents
      </OnThisPage.Header>

      {/* 2. Scroll-Spy Context Wrapper Providers */}
      <AnchorProvider toc={tocItems} single={false}>
        <ScrollProvider containerRef={scrollContainerRef}>
          
          {/* 3. Masked Scrollable Viewport */}
          <OnThisPage.Content ref={scrollContainerRef} className="max-h-[80vh]">
            <OnThisPage.List className="relative border-l border-zinc-200 dark:border-zinc-800">
              
              {/* 4. Active Section Accent Indicator */}
              <OnThisPage.Indicator className="bg-primary-500" />
              
              {/* 5. Hierarchical Link Mapping */}
              {headings.map((h) => (
                <OnThisPage.Item key={h.id} level={h.level}>
                  <OnThisPage.Link href={`#${h.id}`}>
                    {h.text}
                  </OnThisPage.Link>
                </OnThisPage.Item>
              ))}

            </OnThisPage.List>
          </OnThisPage.Content>

        </ScrollProvider>
      </AnchorProvider>
    </OnThisPage>
  )
}

[!TIP] If you don't want to map items manually, you can use the high-level primitive wrapper <OnThisPage.Tree headings={headings} /> to automatically mount the observers, indicators, and list links inside an active layout.


When to use Tree vs the lower-level providerLink

APIWhat you give upWhat you keep
<OnThisPage.Tree headings={headings} />Full control over scroll-mathThe simplest possible integration — one prop in.
<AnchorProvider> + <ScrollProvider> + manual <Items>NothingBespoke observer options and container placement.

Tree is what you want unless you have a strong reason. The internals are eight lines of code total — reach for them only when you're building a sidebar that isn't a list of headings.


PitfallsLink

  • headings must include id. The rehype-slug pipeline assigns IDs to every heading at build time, but if you're rendering on the fly (editor previews, an inline CMS injection), make sure each heading has an anchor ID matching OnThisPage.Link's href.
  • ScrollProvider needs a stable ref. Pass a containerRef={useRef(null)}don't create a new ref every render, or the auto-scroll will jump on every update.
  • single=true switches the scroll-spy to "first matching heading wins" mode. Useful when you have lots of h3 underneath one h1 and want the parent — not the child — to highlight.

Composable Sub-ComponentsLink

The OnThisPage primitive provides the following sub-components for structure customization:

ComponentHTML TagDescriptionProps
OnThisPage.Root / OnThisPage<nav>The outer container wrapper representing the sticky desktop right column navigation area.ComponentBaseProps
OnThisPage.Header<div>A typography title block to label the table of contents.ComponentBaseProps
OnThisPage.Content<div>Scrollable viewport container with hidden scrollbars.OnThisPage.Content Props
OnThisPage.List<ul>List element shell that wraps the item anchors.ComponentBaseProps
OnThisPage.Item<li>Single list item cell wrapper. Indents H3 elements.OnThisPage.Item Props
OnThisPage.Link<a>Anchor link element that intercepts click events to perform smooth viewport scrollings.OnThisPage.Link Props
OnThisPage.Indicator<div>A floating vertical highlight track indicator showing the active anchor link.ComponentBaseProps
OnThisPage.ItemsVariesLoop helper rendering active links and indicators automatically based on heading configs.OnThisPage.Items Props
OnThisPage.Tree<nav>High-level automated component grouping containing all providers, observers, and items.OnThisPage.Tree Props

Component PropsLink

ComponentBaseProps (Common)Link

PropertyTypeDefaultDescription
childrenReactNodeundefinedChildren content elements.
classNamestringundefinedCustom CSS utility classes.
styleCSSPropertiesundefinedInline style overrides.

OnThisPage.Content PropsLink

PropertyTypeDefaultDescription
refRef<HTMLDivElement>undefinedRef instance for the container element.
scrollRefRefObject<HTMLElement>undefinedRef of the main viewport content element to spy scrolling on.

OnThisPage.Item PropsLink

PropertyTypeDefaultDescription
levelnumberundefinedThe heading hierarchy depth. Depth of 3 triggers inner padding.
PropertyTypeDefaultDescription
hrefstringRequiredElement target anchor ID (e.g. '#installation').
activebooleanfalseHighlight active override toggle.
onClick(event) => voidundefinedCustom click intercept callback.

OnThisPage.Items PropsLink

PropertyTypeDefaultDescription
headingsTOCItemType[]RequiredRaw page headers data to map.

OnThisPage.Tree PropsLink

PropertyTypeDefaultDescription
headingsTOCItemType[]RequiredHeaders array mapping for active rendering.

Under-the-Hood ContextsLink

If you are constructing a fully bespoke tracker, you can import and wrap your layout in the custom React context providers exposed via OnThisPage:

Context ProviderDescriptionProps
AnchorProviderWatches scroll events using IntersectionObserver and tracks the active page header.AnchorProviderProps
ScrollProviderAuto-scrolls the table of contents lists to keep the active item aligned in viewport center.ScrollProviderProps

AnchorProviderPropsLink

PropertyTypeDefaultDescription
tocTOCItemType[]RequiredTable of contents headers array to observe.
singlebooleanfalseIf true, only activates one item at a time.
observerOptionsIntersectionObserverInitundefinedIntersectionObserver options override.

ScrollProviderPropsLink

PropertyTypeDefaultDescription
containerRefRefObject<HTMLElement>RequiredRef of scroll container element holding the links list.
Last updated on July 27, 2026

Was this page helpful?