Navigation
Anchor nav
A table of contents that knows where you are — the "On this page" column.
Import
import { AnchorNav } from '@the_viveksingh/vivek-ui'On this page
Real hash links that work before JavaScript, then an IntersectionObserver marks the section in view with aria-current="location". offset accounts for a fixed header in both the spy and the jump. Clicking scrolls smoothly (instantly under prefers-reduced-motion), replaces the hash without a history entry, and moves focus to the section.
Overview
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Installation
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
With npm
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
With pnpm
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Usage
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Theming
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
<AnchorNav
title="On this page"
offset={64}
items={[
{ id: 'overview', label: 'Overview' },
{
id: 'install',
label: 'Installation',
children: [
{ id: 'npm', label: 'With npm' },
{ id: 'pnpm', label: 'With pnpm' },
],
},
{ id: 'usage', label: 'Usage' },
]}
style={{ position: 'sticky', top: '5rem' }}
/>Horizontal, under a page header
orientation="horizontal" turns the rail into one scrolling row with an underline; nested items are hidden there. onActiveChange reports the current id; pass activeId to drive it yourself.
Overview
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Installation
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
With npm
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
With pnpm
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Usage
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Theming
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
Scroll this column. The list on the left follows the heading nearest the top and marks it aria-current="location". Every entry is a real hash link, so it works before JavaScript and copies as a link.
<AnchorNav
orientation="horizontal"
label="Sections"
items={[
{ id: 'summary', label: 'Summary' },
{ id: 'activity', label: 'Activity' },
{ id: 'billing', label: 'Billing' },
{ id: 'members', label: 'Members' },
]}
onActiveChange={(id) => track('section_view', { id })}
/>Props
Generated from the package's own type declarations, so this table cannot drift from the code.
| Prop | Type | Default | Description |
|---|---|---|---|
items | AnchorNavItem[] | — | — |
label | string | — | Accessible name. Defaults to `title` when it is a string, else `'On this page'`. |
title | ReactNode | — | Visible heading above the list. |
offset | number | — | Pixels covered by a fixed header, so jumps and the active section account for it. Default `0`. |
smooth | boolean | — | Animate the jump. Ignored — instant — when the user prefers reduced motion. Default `true`. |
activeId | string | — | Controlled active `id`. |
defaultActiveId | string | — | — |
onActiveChange | (id: string) => void | — | — |
orientation | 'vertical' | 'horizontal' | — | — |
size | 'sm' | 'md' | — | — |
Every remaining prop is spread onto the root element, so all standard HTML and ARIA attributes work. className and style are merged with the library's own, never replaced, and the ref forwards to the root DOM node.
Rendering
AnchorNav declares 'use client' because it needs state, effects or event handlers. Importing it into a Server Component creates a client boundary at this component — everything above it stays on the server.