Scrollspy Table of Contents Rail
Sticky table of contents beside a long article, with a marker that slides to the section in view and a reading-progress line down the rail.
About This Component
A sticky table of contents for documentation, guides and long articles. One ScrollTrigger per section moves a marker down the rail to whichever heading is at reading position, the active label brightens, and a thin progress line fills as the reader works through the piece. Clicking an entry scrolls the page to that section with ScrollToPlugin and moves the keyboard caret with it.
Read the full effect overview
Ships in three looks, vault, paper and corporate, switched by one data-variant attribute and defined entirely in CSS custom properties, so the same script serves all three. One HTML file, one stylesheet, one script, GSAP plus ScrollTrigger and ScrollToPlugin.
What's Included
- One ScrollTrigger per section, so the active entry changes at reading position rather than at the viewport edge
- Marker slides on measured offsets, so labels can wrap to any height
- Reading-progress line scrubbed across the whole article
- Clicking an entry scrolls with ScrollToPlugin and moves focus to that section
- Re-measures after web fonts load and on resize, then refreshes ScrollTrigger
- Works with no JavaScript: the entries are plain anchors and still jump
- Reduced motion keeps every state change but drops the travel time to zero
- Three shipped variants: vault, paper, corporate
Perfect For
- Documentation and API reference sidebars
- Long-form editorial and research articles
- Legal pages, changelogs and release notes
- Onboarding guides with numbered steps
How It Works
One trigger per section
Each section gets a ScrollTrigger whose start and end sit on a band a third of the way down the viewport. onToggle fires when that band is inside the section, which is what makes the rail change at reading position instead of when a heading first appears at the bottom of the screen.
A measured marker
The marker carries no CSS transform and keeps top: 0, so the y GSAP tweens is the entire offset. Each move measures the target link against the rail with getBoundingClientRect and tweens both y and height, which means a label that wraps to two lines gets a taller marker rather than a misaligned one. A CSS top or margin on the marker would be added to the measurement and double the offset.
Progress and clicks
A single scrubbed ScrollTrigger over the article sets the progress line's scaleY from its own progress value, so the fill is exact rather than interpolated from the active section. Clicking an entry cancels the default jump and hands the scroll to ScrollToPlugin with an offset for the sticky header, then focuses the destination section so a keyboard user carries on from there.