All hooks / Viewport and media
useScrollSpy
The id of the section the user is reading: the last of ids whose element has scrolled past the top of the viewport (plus offset). At the bottom of the page the last section is active, even if it is too short to reach the top. null before the first section, on the server, and during hydration.
Re-renders only when the active section changes. Sections are looked up by id on each scroll, so they can mount later.
Import
import { useScrollSpy } from '@vincent.ri/react-tils'
// or only this hook
import { useScrollSpy } from '@vincent.ri/react-tils/useScrollSpy'Usage
const ids = ['install', 'usage', 'api']
const active = useScrollSpy(ids, { offset: 64 })
return (
<nav>
{ids.map((id) => (
<a key={id} href={`#${id}`} aria-current={active === id ? 'location' : undefined}>{id}</a>
))}
</nav>
)Live demo
Scroll the page: the sidebar highlights the section you're reading.
When to use it
Highlighting the current section in a table of contents, a docs sidebar, or step navigation on a long page.
When not to
The sections scroll inside a container rather than the page; this follows the window. To know whether one element is on screen, use useInView.
API
function useScrollSpy(ids: readonly string[], options?: ScrollSpyOptions): string | null| Parameter | Description |
|---|---|
ids | Element ids of the sections, in page order. |
options | offset: height of a sticky header in pixels. Optional. |
Returns The active id, or null.
Types
export type ScrollSpyOptions = {
/** Distance in pixels from the top of the viewport to the line a section must cross to become active, e.g. a sticky header's height. Default `0`. */
offset?: number;
};Browser support
Uses only long-established browser features, so it works wherever React does.
First version with support, from MDN's compatibility data (web-features 3.42.0, @mdn/browser-compat-data 8.1.5).
Coming from another library?
Other libraries call this useActiveSection, useScrollspy.