Skip to content

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.

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

NameTypeDefaultDescriptionVersion
itemsAnchorItem[]undefinedThe links as a tree. AnchorLink children render after them.Unreleased
valuestring | nullundefinedThe current link’s href. Setting it controls the anchor.Unreleased
defaultValuestring | nullnullInitial current link when uncontrolled.Unreleased
onUpdateValue(value: string | null) => voidundefinedCalled with the new current href, on scroll and on click.Unreleased
typerail | blockrailHow the current link is marked.Unreleased
showRailbooleantrueDraws the rail (rail type only).Unreleased
showBackgroundbooleantrueTints the current link (with the rail only).Unreleased
boundnumber12How 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
ignoreGapbooleanfalseKeeps the last reached link current between targets.Unreleased
offsetTargetstring | HTMLElement | Window | Document | RefObject | (() => HTMLElement | Window | Document | null)windowThe scroll container the targets live in: a selector, an element, a ref or a function.Unreleased
aria-labelstringlocale Anchor.labelNames the nav landmark (“On this page”).Unreleased
NameTypeDefaultDescriptionVersion
hrefstringThe target, as #id.Unreleased
titleReactNodeundefinedThe link text. A string title is also the tooltip when it is cut off.Unreleased
childrenReactNodeundefinedNested 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.