Skip to content

Anchor Nav

Navigate a long page with a table of contents that highlights the current section.

import { AnchorNav, AnchorNavItem } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use it beside a long page people read and come back to: a guide, a policy, release notes, a settings page with many groups. It shows where the reader is and takes them to any section in one click. It's a scrollspy over one page. To move between pages, use a Sidebar or Tabs, and for a flow of steps, a Stepper Nav.

import { AnchorNav, type AnchorNavItem } from '@youj/react'

const items: AnchorNavItem[] = [
  { id: 'overview', label: 'Overview' },
  { id: 'install', label: 'Installation' },
  { id: 'npm', label: 'From npm', depth: 1 },
  { id: 'theming', label: 'Theming' },
]

export function Guide() {
  return (
    <div className="grid gap-10 lg:grid-cols-[1fr_14rem]">
      <article>
        <section id="overview">…</section>
        <section id="install">…</section>
      </article>
      <aside className="hidden lg:block">
        <AnchorNav items={items} className="sticky top-20" offset={64} />
      </aside>
    </div>
  )
}

Each item points at the id of a section on the page, and depth indents a sub-section (up to two levels). The section in view is the last one whose top has passed a reading line a quarter of the way down the window. At the very bottom of the page, the last section counts as in view, even if it's too short to reach the line.

Choosing a link scrolls smoothly to its section, moves focus there without scrolling again, and puts its id in the URL. While that scroll runs, the bar goes straight to the destination instead of stepping through every section on the way. A wheel or touch mid-scroll hands control back to the reader. The page tracks the window by default. Pass scrollContainer when the sections scroll inside an element, as in the previews.

Reference

Propsitems, title, scrollContainer… (4)

Props

AnchorNav props
PropType
itemsRequiredAnchorNavItem[]–
titleReactNode

A caption above the list. Default "On this page"; null for none.

–
scrollContainerRefObject<HTMLElement | null>

The element that scrolls the sections. Default: the page.

–
offsetnumber

Space kept above a section: where a chosen one lands (clear a sticky header with it).

default 16

16
Other props pass through: extends ComponentProps<'nav'>.
MotionCurrent bar, link color, scroll to a section · reduced motion

Motion

Motion tokens
MomentTokens
Current barone Sliding Indicator on the list’s start edge glides to the section in view
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
Link colorthe current link and hovered links turn to ink
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Scroll to a sectionthe browser’s smooth scroll; the bar goes straight to the destination
no token

native smooth

native smooth

Reduced motion

The page jumps to the chosen section and the bar jumps with it.

Reduced motion

The page jumps straight to the chosen section instead of scrolling, and the bar moves at once. Focus, the URL and the tracking work the same.

AccessibilityKeyboard, ARIA, focus

Accessibility

  • It's a nav landmark named by title ("On this page"), or by aria-label when there's no text title.
  • The link for the section in view carries aria-current="location", so screen readers announce it as the current location.
  • Following a link moves focus to the section (giving it tabindex="-1" if it can't take focus), so the next Tab continues from there and a screen reader starts reading it. Give sections a visible focus style.
  • The links are plain anchors with href="#id": ⌘-click or middle-click opens them the browser's way, and they work before the page hydrates.