Component

Scrollspy

Published on

Description anchor

A scrollspy highlights the navigation link for the section currently in view, giving the reader a persistent sense of place within a long document. It wraps a table of contents whose links are in-page anchors; as the reader scrolls, the link for the section in the top half of the viewport is marked as current.

The pattern is progressive: with no scripting the navigation is still a plain list of working jump links. The ds-scrollspy web component lazy-loads and layers the active highlighting on top using an IntersectionObserver.

This is a transitional implementation. The vanilla styles already set scroll-target-group: auto on the navigation and highlight the active link with the native :target-current selector. The JavaScript component only defines itself when CSS.supports("scroll-target-group", "auto") is false, so it removes itself automatically once native CSS scroll-target-group reaches baseline support.

Example anchor

Open in new tab(opens in new tab)
View HTML markup
  <nav aria-labelledby="nav-scrollspy-label" is="ds-scrollspy">
  <section>
    <header>
      <h2 id="nav-scrollspy-label">On this page</h2>
    </header>
    <ol>
      <li><a href="#introduction">Introduction</a></li>
      <li><a href="#usage">Usage</a></li>
      <li><a href="#accessibility">Accessibility</a></li>
    </ol>
  </section>
</nav>
<section>
  <h2 id="introduction">Introduction</h2>
  <p>Scroll the frame to see the matching link highlight as each section reaches the top half of the viewport.</p>
</section>
<section>
  <h2 id="usage">Usage</h2>
  <p>Each link points to the id of a section heading. The active link is marked as you read down the page.</p>
</section>
<section>
  <h2 id="accessibility">Accessibility</h2>
  <p>The link for the section in view receives aria-current="true" so assistive technology announces the current location.</p>
</section>

Usage anchor

Wrap the table of contents in <nav is="ds-scrollspy">. Each <a href="#id"> points at the id of a section heading, and every target section needs a matching <h2 id> so the observer can track it. The observer looks up section:has(h2[id]) first, then falls back to any element carrying that id. A page opened at a #hash, and later hashchange events, set the active link too.

  <nav is="ds-scrollspy" aria-labelledby="toc-label">
  <section>
    <header><h2 id="toc-label">On this page</h2></header>
    <ol>
      <li><a href="#introduction">Introduction</a></li>
      <li><a href="#usage">Usage</a></li>
      <li><a href="#accessibility">Accessibility</a></li>
    </ol>
  </section>
</nav>

<section>
  <h2 id="introduction">Introduction</h2>
</section>
<section>
  <h2 id="usage">Usage</h2>
</section>
<section>
  <h2 id="accessibility">Accessibility</h2>
</section>

In Svelte, use NavScrollspy with a children snippet holding the <Ol> of links. It accepts labelHeader (the heading text, default On this page), labelId, and is (default ds-scrollspy).

Variants anchor

Two implementations of the same ds-scrollspy element ship with the design system. Both register the same custom element name, so the behaviour is chosen by loading one implementation or the other, not by changing the is attribute.

  • ds-scrollspy — the default. It tracks every section independently, so more than one link can be current at once when several short sections share the top half of the viewport. Best for a long table of contents.
  • ds-scrollspy-single — keeps exactly one link current at a time, choosing the nearest heading from the scroll direction. Prefer it when a single active item reads more clearly, such as a short list.

Accessibility anchor

  • The active link receives aria-current="true" and it is removed from the others, so assistive technology announces the section currently in view.
  • The navigation is labelled with aria-labelledby pointing at its On this page heading, making it identifiable in a list of landmarks.
  • Links are real in-page anchors, so they are keyboard focusable and operable with or without the script — the highlight is an enhancement, never a requirement for navigation.
  • The default variant can mark several links current at once; prefer ds-scrollspy-single when a single announced location is less verbose.

References anchor

  • Documentation — the two column layout that hosts the scrollspy in its aside
  • Links — the underlying in-page anchor pattern

Variables anchor

  • labelHeader
  • labelId
  • is