Anchor

Free

An "on this page" navigation list that tracks scroll position with an IntersectionObserver and smooth-scrolls to a section on click. Vertical (side-rail) and horizontal (pill-row) layouts.

</>sv-anchor anchorscrollspytable of contentstocnavigationon this page
import { SvAnchorComponent } from '@styloviz/anchor';

Demo & usage

Basic usage

Usage example — open the gallery for the full interactive demo.

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Horizontal pill row

example.htmlAngular
<sv-anchor [items]="sections" orientation="horizontal" />

Custom scroll container + offset

example.htmlAngular
<sv-anchor [items]="sections" [root]="scrollContainer()" [offset]="72" (activeIdChange)="onSectionChange($event)" />

When to use

  • Job: document position — renders real hash links and reports where the reader is via `activeIdChange` (compare all seven in the “Choosing a navigation pattern” guide under Guides).
  • A long docs or article page where readers benefit from a persistent "on this page" outline.
  • You want the outline to highlight itself as the reader scrolls, without wiring up scroll listeners by hand.
  • For a fixed set of in-page tabs that switch content rather than scroll to it, use Tabs instead.

Async states

None — this component has nothing to load, so it has no loading, empty or error state to drive.

Inputs

NameTypeDefaultDescription
itemsrequiredreadonly AnchorItem[]—The sections to list and track, in document order.
rootHTMLElement | nullnullScroll container the sections live in. `null` = the window/viewport.
orientationAnchorOrientation'vertical'Layout axis.
offsetnumber0Extra pixels kept between a jumped-to section and the top of the scroll container — room for a sticky header the section would otherwise land underneath.
offsetFromHTMLElement | nullnullA sticky header to measure instead of using a fixed `offset` — its current height becomes the offset, and follows it as it changes.
activationAnchorActivation'last-passed'Which reading of "current" to highlight: `'last-passed'` (the last heading scrolled past), `'first-visible'`, `'most-visible'`, or `'nearest-offset'` (the heading closest to the sticky-header line).
urlKeystring | nullnullReflect the section being read into the URL under this key — pair it with `svHashNavLocation('section')` for `#installation`. Needs `provideSvNavLocation()` at the app root.
urlWriteModeSvNavWriteMode'replace'What a CLICK on a nav link does to history — `'push'` makes back return to the section the reader came from. Scrolling always replaces, whatever this says.

Outputs

EventPayload typeDescription
activeIdChangestring | nullEmits whenever the active section changes, including on click-to-jump.
unresolvedSvUnresolvedValue<string>Emitted when an item points at a section this page does not have exactly one of. `'unknown'` for no element, `'blocked'` for several with the same id.

Customization & theming

  • Vertical (bordered side-rail) or horizontal (pill-row) orientation.
  • `offset` shifts the smooth-scroll landing position, e.g. to clear a sticky header.
  • `root` scopes both the IntersectionObserver and the scroll-to call to a custom scrollable container instead of the window.
  • `activeIdChange` emits whenever the active section changes, for syncing external UI.

Best practices

  • Do: Give every linked section a stable `id` that matches the anchor item's `id` — the component resolves targets with `document.getElementById`.
  • Do: Pass `root` when the page scrolls inside a custom container rather than the window, so the scrollspy observes the right viewport.
  • Tip: Tune `rootMargin` behavior indirectly via `offset` when a sticky header covers the top of the viewport and sections trigger active state too late.
  • Don't: Don't use it for fewer than 3–4 sections — a short page doesn't need an outline, and the scrollspy machinery is wasted.

Accessibility

  • Rendered as a labelled `nav` landmark ("On this page").
  • The active link is marked with `aria-current="true"`.
  • Respects `prefers-reduced-motion`: falls back to an instant jump instead of a smooth scroll.
  • Links are real anchors with real `href`s (`#section-id`), so they work without JavaScript and are keyboard-focusable in document order.

Feedback on Anchor

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.