Skip to content
Tier

Section nav

Pro

The page's sections as a list of links ("On this page"), the one being read marked aria-current="location" as the page scrolls (nothing is announced: scrolling isn't news). Each is a real link to #id, so it can be opened in a new tab or copied. Choosing one scrolls to the section, offset pixels clear of a sticky header, and moves focus to its heading (or to the section, made focusable with tabIndex={-1}), so a keyboard or screen-reader user lands where they asked rather than at the top of the page. The address's #id changes to the section's, replacing the history entry, so Back leaves the page rather than stepping through its sections.

Give root when the sections scroll inside a container rather than the page. A section whose element isn't on the page is left out of the list, with a warning in development, until it arrives: the page is looked at again a moment after it changes, so sections drawn after a fetch or behind Suspense get their links when they appear.

Markdown
Version 5.2.0· other versions

This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0 and 4.2.0: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/section-nav

Brings in: locale, motion, tokens, utils

Usage

TSX · The example above
"use client"

import * as React from "react"
import { SectionNav } from "@/components/candor/section-nav"

const sections = [
  { id: "section-nav-overview", label: "Overview" },
  { id: "section-nav-access", label: "Who can see it" },
  { id: "section-nav-roles", label: "Roles", level: 3 as const },
  { id: "section-nav-retention", label: "How long it's kept" },
]

export default function Example() {
  const scroller = React.useRef<HTMLDivElement>(null)
  return (
    <div className="flex max-w-2xl gap-4 rounded-xl border bg-card p-4">
      <div ref={scroller} className="h-64 flex-1 overflow-y-auto pe-2 text-sm">
        {sections.map((s) => (
          <section key={s.id} id={s.id} className="flex min-h-40 flex-col gap-2 py-2">
            {s.level === 3 ? <h4 className="font-medium">{s.label}</h4> : <h3 className="font-semibold">{s.label}</h3>}
            <p className="text-muted-foreground">
              What the policy says about {s.label.toLowerCase()}, written out in full so the section is tall enough to scroll past.
            </p>
          </section>
        ))}
      </div>
      <SectionNav sections={sections} root={scroller} className="w-44 shrink-0" />
    </div>
  )
}

<SectionNav>

PropTypeDefaultDescription
sectionsrequiredSectionNavItem[]—The sections, in page order.
labelstring | undefined—The navigation's accessible name. "On this page" by default.
offsetnumber | undefined0Pixels of a sticky header over the top of the scroll area: a section counts as reached below it, and is scrolled to just under it.
rootReact.RefObject<HTMLElement | null> | undefined—The element the sections scroll in, when it isn't the page.
onNavigate((id: string) => void) | undefined—Called with a section's id when the person chooses it, after focus has moved.
classNamestring | undefined—Classes for the navigation, merged with Candor's.

Strings

The fixed words, in English by default. Change any of them under sectionNav in <LocaleProvider strings={{ … }}>; the table is exported as sectionNavStrings.

KeyEnglish
labelOn this page

Motion

MomentFamilyWhat it does
scrollmoveChoosing a section scrolls the page (or `root`) to it smoothly. Under reduced motion, the calm level or `data-motion="none"` it jumps there at once. Either way focus lands on the section's heading.

Set with --cd-motion-section-nav-<moment>-* in your brand file; the families are in the design reference.

Types

SectionNavItem
One section of the page `SectionNav` lists: the id of its element, the words for it, and its heading level.
{ /** The `id` of the section's element (or its heading) on the page; the link's `#id`. */ id: string; /** The words for it in the list, usually its heading's. */ label: string; /** 2 (the default) for a section, 3 for one inside it, drawn indented. */ level?: 2 | 3 | undefined; }