Scrollspy

Scrollspy automatically highlights the nav link for whichever section of a long page is currently in view, updating as the user scrolls. It's the mechanism behind a "sticky side table of contents" that tracks reading position without any manual click handling. We'll build one for a TrailMap trail-guide page with an Overview/Difficulty/Directions/Reviews side-nav.

Setting Up Scrollspy: data-spy="scroll"

Scrollspy watches a scrollable container. Put data-spy="scroll" on that container, plus an id so the nav can reference it, and give it a fixed height with overflow-y: scroll in CSS (or spy on body for the whole page).

<body data-spy="scroll" data-target="#guideNav" data-offset="60"> ... </body>
Note: This is data-spy, not Bootstrap 5's data-bs-spy. Every attribute here — data-spy, data-target, data-offset — keeps the un-prefixed Bootstrap 4 naming.

Pointing at the Nav: data-target

data-target on the spied container points at the id of the nav element whose links should get highlighted. Each link in that nav must have an href matching a section's id (e.g. "#overview").

<nav id="guideNav" class="nav flex-column">
  <a class="nav-link" href="#overview">Overview</a>
  <a class="nav-link" href="#difficulty">Difficulty</a>
</nav>

Requirements for Scrollspy to Work

A few things have to line up or nothing gets highlighted: each target section needs a real id, the nav links' href values must match those ids exactly (including the #), and the nav should use .nav-link items so Bootstrap knows which elements to toggle .active on. No initialization call is required beyond loading bootstrap.min.js — Scrollspy attaches itself automatically from the data-spy attribute on page load.

Worked Example: TrailMap Trail Guide Page With Sticky Side-Nav

A single trail guide page with four sections — Overview, Difficulty, Directions, Reviews — and a side-nav that stays highlighted on whichever section the hiker is currently reading.

<div class="row">

  <!-- Sticky side-nav -->
  <div class="col-md-3">
    <nav id="guideNav" class="nav flex-column sticky-top" style="top: 70px;">
      <a class="nav-link" href="#overview">Overview</a>
      <a class="nav-link" href="#difficulty">Difficulty</a>
      <a class="nav-link" href="#directions">Directions</a>
      <a class="nav-link" href="#reviews">Reviews</a>
    </nav>
  </div>

  <!-- Scrollable guide content, spied on -->
  <div class="col-md-9" data-spy="scroll" data-target="#guideNav" data-offset="20"
    style="height: 500px; overflow-y: scroll; position: relative;">
    <h4 id="overview">Overview</h4>
    <p>Ridgeline Loop is a 4.2 mi out-and-back with sweeping valley views from the ridge.</p>

    <h4 id="difficulty">Difficulty</h4>
    <p>Rated Moderate: 620 ft of elevation gain over switchbacks near the summit.</p>

    <h4 id="directions">Directions</h4>
    <p>Park at the north trailhead lot; the marked entrance is just past the ranger station.</p>

    <h4 id="reviews">Reviews</h4>
    <p>4.7 out of 5 from 132 hikers — most-praised for the summit viewpoint.</p>
  </div>

</div>

As the hiker scrolls the right-hand column, Scrollspy watches which heading is currently near the top of the scrollable area and adds .active to the matching link in #guideNav — no click handlers, no manual JS.

Up next: That wraps up the jQuery-powered interactive components. Next, the Jumbotron lesson starts a short run of Bootstrap-4-only patterns that later got removed or renamed in Bootstrap 5.