Anchor
Anchor is a table of contents that follows scrolling, like a page’s “On this page” outline. Each link points at an element by #id; the link whose target was scrolled to last is current and carries aria-current="location". Clicking a link scrolls its target into view (smoothly, unless the user prefers reduced motion) and puts its hash in the address bar without the browser’s jump.
Demos
Basic
Pass the links as an items tree. offsetTarget names the element that scrolls; without it the anchor follows the page. Once the panel can’t scroll further, the last section in view becomes current even though it never reaches the top.
AnchorLink
The same outline by composition: nest AnchorLinks under Anchor.
Rail and background
showRail draws the rail with a mark beside the current link; showBackground adds the tint behind it.
Block
type="block" drops the rail and highlights the current link as a block.
Ignore gap
Here the targets are headings with space between them. By default a link is current only while its heading is at the top; with ignoreGap (left) the last heading passed stays current through the gap.
Controlled
value sets the current link and onUpdateValue reports every change, from scrolling or a click.
Anchor Props
| Name | Type | Default | Description | Version |
|---|---|---|---|---|
items | AnchorItem[] | undefined | The links as a tree. AnchorLink children render after them. | Unreleased |
value | string | null | undefined | The current link’s href. Setting it controls the anchor. | Unreleased |
defaultValue | string | null | null | Initial current link when uncontrolled. | Unreleased |
onUpdateValue | (value: string | null) => void | undefined | Called with the new current href, on scroll and on click. | Unreleased |
type | rail | block | rail | How the current link is marked. | Unreleased |
showRail | boolean | true | Draws the rail (rail type only). | Unreleased |
showBackground | boolean | true | Tints the current link (with the rail only). | Unreleased |
bound | number | 12 | How far below the container’s top, in px, a target counts as reached. Measured from the target’s scroll-margin-top, so a sticky header’s margin moves the line too. | Unreleased |
ignoreGap | boolean | false | Keeps the last reached link current between targets. | Unreleased |
offsetTarget | string | HTMLElement | Window | Document | RefObject | (() => HTMLElement | Window | Document | null) | window | The scroll container the targets live in: a selector, an element, a ref or a function. | Unreleased |
aria-label | string | locale Anchor.label | Names the nav landmark (“On this page”). | Unreleased |
AnchorLink Props
| Name | Type | Default | Description | Version |
|---|---|---|---|---|
href | string | The target, as #id. | Unreleased | |
title | ReactNode | undefined | The link text. A string title is also the tooltip when it is cut off. | Unreleased |
children | ReactNode | undefined | Nested AnchorLinks. | Unreleased |
AnchorItem is { href: string; title: ReactNode; key?: Key; children?: AnchorItem[] }.
The current link can be controlled (value), and clicking replaces the hash instead of jumping, so the outline doesn’t add a history entry per click. To keep the anchor on screen, wrap it in a position: sticky element; to move to a link, set value or scroll the target yourself.