OnThisPage

A table of contents navigation panel that displays and highlights the current page's headings.

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

The OnThisPage primitive is style-neutral: it provides structure, the IntersectionObserver scroll-tracking, the active-track indicator math, and data-* state attributes. All visuals belong to your theme — colors, borders, indentation, the active highlight. Style states with Tailwind v4 variants (data-active:text-primary-500) or plain CSS ([data-active] { ... }).

Composable Scroll Tracker ExampleLink

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

tsx
// docs/components/CustomTOC.tsx
import { useRef } from 'react'
import {
  OnThisPage,
  AnchorProvider,
  ScrollProvider,
  useActiveAnchors,
} 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 the headings database to a standard TOC structure
  const tocItems = headings.map((h) => ({
    title: h.text,
    url: `#${h.id}`,
    depth: h.level,
  }))

  return (
    <OnThisPage className="hidden xl:block w-toc py-4 pl-6 pr-4">
      {/* 1. Static Title Header */}
      <OnThisPage.Header className="text-xs font-bold uppercase tracking-wider text-muted mb-4">
        Table of Contents
      </OnThisPage.Header>

      {/* 2. Scroll Tracker Context Wrapper Providers */}
      <AnchorProvider toc={tocItems} single={false}>
        <ScrollProvider containerRef={scrollContainerRef}>
          {/* 3. Scrollable Viewport */}
          <OnThisPage.Content
            ref={scrollContainerRef}
            className="max-h-[80vh] pb-12"
            fadeClassName="bg-linear-to-b from-transparent to-main"
          >
            {/* 4. List + active-track indicator */}
            <OnThisPage.List className="relative border-l border-subtle space-y-0.5 text-sm">
              <OnThisPage.Indicator className="-left-px w-0.5 rounded-full bg-primary-500" />
              {headings.map((h) => (
                <OnThisPage.Item
                  key={h.id}
                  level={h.level}
                  className="data-[level=3]:pl-3"
                >
                  <OnThisPage.Link
                    href={`#${h.id}`}
                    className="block py-0.5 pl-4 text-[13px] transition-colors text-muted hover:text-body data-active:text-primary-500"
                  >
                    {h.text}
                  </OnThisPage.Link>
                </OnThisPage.Item>
              ))}
            </OnThisPage.List>
          </OnThisPage.Content>
        </ScrollProvider>
      </AnchorProvider>
    </OnThisPage>
  )
}

[!TIP] If you don't want to map the 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. Tree accepts className (list), itemClassName, linkClassName, indicatorClassName, contentClassName, and fadeClassName slots.


State Attributes (data-*)Link

The OnThisPage primitives expose their state through data-* attributes, present only when true. Decide your own colors and treatments from CSS.

AttributeEmitted OnPresent When
data-activeOnThisPage.Link anchorThe heading is the currently active section
data-levelOnThisPage.ItemHeading hierarchy depth (e.g. 2, 3) — use data-[level=3]:pl-3 for indentation
data-otp-rootOnThisPage.RootAlways
data-otp-contentOnThisPage.ContentAlways
data-otp-listOnThisPage.ListAlways
data-otp-fadeContent bottom-fade elementAlways
data-otp-indicatorOnThisPage.IndicatorAlways
aria-currentOnThisPage.Link anchor"true" when active
tsx
<OnThisPage.Tree
  headings={headings}
  className="space-y-0.5 text-sm border-l border-subtle"
  itemClassName="data-[level=3]:pl-3"
  linkClassName="block py-0.5 pl-4 text-[13px] transition-colors text-muted hover:text-body data-active:text-primary-500"
  indicatorClassName="-left-px w-0.5 rounded-full bg-primary-500"
  fadeClassName="bg-linear-to-b from-transparent to-main"
/>

Composable SubcomponentsLink

The OnThisPage primitive provides the following subcomponents for structure customization:

ComponentHTML TagDescriptionProps
OnThisPage.Root / OnThisPage<nav>The outer container wrapper representing the fixed desktop right-column navigation area.ComponentBaseProps
OnThisPage.Header<div>A typographic title block for labeling the table of contents.ComponentBaseProps
OnThisPage.Content<div>Scrollable viewport container (the scroll-spy boundary).OnThisPage.Content Props
OnThisPage.List<ul>List element wrapper wrapping the item anchors.ComponentBaseProps
OnThisPage.Item<li>Individual list cell wrapper. Exposes data-level.OnThisPage.Item Props
OnThisPage.Link<a>Anchor link element intercepting click events to perform smooth viewport scrolling.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 settings.OnThisPage.Items Props
OnThisPage.Tree<nav>Automated high-level component grouping all providers, observers, and items.OnThisPage.Tree Props

Component PropsLink

ComponentBaseProps (Common)Link

PropertyTypeDefaultDescription
childrenReactNodeundefinedChild content elements.
classNamestringundefinedCustom utility CSS 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 for tracking scroll.
fadeClassNamestringundefinedClasses for the bottom fade element (theme-owned).

OnThisPage.Item PropsLink

PropertyTypeDefaultDescription
levelnumberundefinedThe heading hierarchy depth — exposed via data-level.
PropertyTypeDefaultDescription
hrefstringRequiredTarget anchor id of the element (e.g., '#installation').
activebooleanfalseActive highlight override switch — exposed via data-active + aria-current.
onClick(event) => voidundefinedCustom click interception callback.

OnThisPage.Items PropsLink

PropertyTypeDefaultDescription
headingsTOCItemType[]RequiredRaw page heading data to map.
classNamestringundefinedClasses for the <ul>.
itemClassNamestringundefinedClasses for each <li>.
linkClassNamestringundefinedClasses for each <a> (style states via data-active).
indicatorClassNamestringundefinedClasses for the active-track indicator.

OnThisPage.Tree PropsLink

PropertyTypeDefaultDescription
headingsTOCItemType[]RequiredHeadings array for active rendering.
classNamestringundefinedClasses for the <ul> (passed through to Items).
itemClassNamestringundefinedClasses for each <li>.
linkClassNamestringundefinedClasses for each <a>.
indicatorClassNamestringundefinedClasses for the active-track indicator.
contentClassNamestringundefinedClasses for the scrollable content container.
fadeClassNamestringundefinedClasses for the bottom fade element.

Internal ContextsLink

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

Context ProviderDescriptionProps
AnchorProviderObserves scroll events using IntersectionObserver and tracks the active page heading.AnchorProviderProps
ScrollProviderAuto-scrolls the table of contents lists to keep the active item centered in the viewport.ScrollProviderProps

AnchorProviderPropsLink

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

ScrollProviderPropsLink

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

Was this page helpful?