Scrollspy

Highlight the nav link for whichever page section is currently in view.

Basic Usage

Wrap just your nav in <c-ui.scrollspy> and bind your links to isActive('id'). The tracked ids are read from your links' # hrefs, so each section just needs a matching id anywhere in your layout. It tracks whatever scrolls, the page or a scrolling panel the sections sit in like the one below, detected automatically.

Links call scrollTo('id') to smooth-scroll into view. Add scroll-mt-* to a section to offset where it lands, e.g. clear of a sticky header.

Introduction

Scrollspy keeps a table of contents in sync with the reader's position. As each section below crosses the top of this panel, its link on the left lights up automatically. Scroll slowly to watch the active state hand off from one to the next.

Installation

Wrap your nav in the component and give each section a matching id. The sections can sit anywhere, there is no scroll-position maths to wire up by hand.

Usage

The section whose heading last crossed the top of the panel wins. Bind your link styling to isActive('id') and the highlight follows the scroll, while a click smooth-scrolls that section into view.

Summary

Mark sections, bind links and the scrollspy does the rest. Add a scroll-mt-* utility, as on each section here, when you want a little breathing room above the heading rather than landing it flush.

<c-ui.scrollspy>
    <nav class="flex flex-col gap-2">
        <a href="#intro" @click.prevent="scrollTo('intro')" :class="isActive('intro') ? 'text-accent font-medium' : 'text-ink-500'">Introduction</a>
        <a href="#install" @click.prevent="scrollTo('install')" :class="isActive('install') ? 'text-accent font-medium' : 'text-ink-500'">Installation</a>
        <a href="#usage" @click.prevent="scrollTo('usage')" :class="isActive('usage') ? 'text-accent font-medium' : 'text-ink-500'">Usage</a>
    </nav>
</c-ui.scrollspy>

{# Sections live anywhere, each just needs an id matching its link. The scroll #}
{# container (the page, or a scrolling panel) is detected automatically. #}
<section id="intro" class="scroll-mt-4">...</section>
<section id="install" class="scroll-mt-4">...</section>
<section id="usage" class="scroll-mt-4">...</section>
{% cotton ui.scrollspy %}
    <nav class="flex flex-col gap-2">
        <a href="#intro" @click.prevent="scrollTo('intro')" :class="isActive('intro') ? 'text-accent font-medium' : 'text-ink-500'">Introduction</a>
        <a href="#install" @click.prevent="scrollTo('install')" :class="isActive('install') ? 'text-accent font-medium' : 'text-ink-500'">Installation</a>
        <a href="#usage" @click.prevent="scrollTo('usage')" :class="isActive('usage') ? 'text-accent font-medium' : 'text-ink-500'">Usage</a>
    </nav>
{% endcotton %}

{# Sections live anywhere, each just needs an id matching its link. The scroll #}
{# container (the page, or a scrolling panel) is detected automatically. #}
<section id="intro" class="scroll-mt-4">...</section>
<section id="install" class="scroll-mt-4">...</section>
<section id="usage" class="scroll-mt-4">...</section>

API Reference

Scrollspy

Name Description Type Options Default
root Optional CSS selector for the scrolling element. Defaults to auto-detecting the sections' scroll container (the page, or their nearest scrolling ancestor); set it only for edge cases auto-detection can't resolve, e.g. root='#docs-panel'. str auto-detect
class Additional classes merged onto the wrapper element. str
slot (default) Your nav. Bind each link's active styling to isActive('id'), or use nav / navlist items with a spy attribute.

Exposes (Alpine)

Name Description Type Options Default
active The id of the section currently in view. str null
isActive(id) Returns true when the given section id is the active one. bool
scrollTo(id) Smooth-scrolls the matching section into view, in the page or its scroll container.

Sections

Name Description Type Options Default
id Each section needs an id matching the href of its nav link. That's all that's required. attr
data-spy-section Optional. Tracked ids are read from your nav links, so this is only needed to track a section that has no link. attr
scroll-mt-* Optional. A scroll-margin-top utility on a section sets how far below the top it lands when scrolled to, so a sticky header won't cover it. Respected by both anchor jumps and scrollTo. class

Nav / Navlist item

Name Description Type Options Default
spy Section id to track. Inside a scrollspy, the item auto-binds isActive and scrollTo and defaults its href to #<spy>. str