Scrollspy

Automatically update navigation or list group components based on scroll position to indicate which link is currently active in the viewport.

Create amazing Typeform-like forms and pages just by writing Markdown!

Let's start with a basic navbar example. Scroll the area below the navbar and watch the active class change. Open the dropdown menu and watch the dropdown items be highlighted as well. You can find detailed explanations about how it works after this example.

About #

Plans #

Page builder #

Form builder #

HTML
<!-- Navbar scrollspy -->
<nav id="scrollspy-1-navbar" class="navbar px-3" style="background-color: var(--bs-content-bg); border-bottom: var(--bs-border-width) solid var(--bs-content-border-color);">
  <a class="navbar-brand" href="#">
    <img src="..." alt="Logo" width="24" height="24" class="d-inline-block align-text-top">
    <span class="d-none d-sm-inline">Builder</span>
  </a>
  <ul class="nav nav-pills">
    <li class="nav-item">
      <a class="nav-link" href="#scrollspy-1-about">About</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" href="#scrollspy-1-pricing">Plans</a>
    </li>
    <li class="nav-item dropdown">
      <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Products</a>
      <ul class="dropdown-menu dropdown-menu-end">
        <li><h6 class="dropdown-header">Our products</h6></li>
        <li><a class="dropdown-item" href="#scrollspy-1-page-builder">Page builder</a></li>
        <li><a class="dropdown-item" href="#scrollspy-1-form-builder">Form builder</a></li>
      </ul>
    </li>
  </ul>
</nav>
<div data-bs-spy="scroll" data-bs-target="#scrollspy-1-navbar" data-bs-root-margin="0px 0px -40%" data-bs-smooth-scroll="true" class="bg-body p-3 overflow-y-auto" tabindex="0" style="max-height: 300px;">
  <h4 id="scrollspy-1-about">
    About
    <a href="#scrollspy-1-about" class="fw-normal text-decoration-none ms-1">#</a>
  </h4>
  <div>
    ...
  </div>
  <h4 id="scrollspy-1-pricing">
    Plans
    <a href="#scrollspy-1-pricing" class="fw-normal text-decoration-none ms-1">#</a>
  </h4>
  <div>
    ...
  </div>
  <h4 id="scrollspy-1-page-builder">
    Page builder
    <a href="#scrollspy-1-page-builder" class="fw-normal text-decoration-none ms-1">#</a>
  </h4>
  <div>
    ...
  </div>
  <h4 id="scrollspy-1-form-builder">
    Form builder
    <a href="#scrollspy-1-form-builder" class="fw-normal text-decoration-none ms-1">#</a>
  </h4>
  <div>
    ...
  </div>
</div>

How it works #

Scrollspy toggles the .active class on anchor (<a>) elements when the element with the id referenced by the anchor's href is scrolled into view. Scrollspy is best used in conjunction with a navigation component or list group, but it will also work with any anchor elements in the current page. Here's how it works:

  • To start, scrollspy requires two things: a navigation, list group, or a simple set of links, plus a scrollable container. The scrollable container can be the <body> or a custom element with a set height and overflow-y: scroll.
  • On the scrollable container, add data-bs-spy="scroll" and data-bs-target="#{id}" where {id} is the unique id of the associated navigation. If there is no focusable element inside the element, be sure to also include a tabindex="0" to ensure keyboard access.
  • As you scroll the "spied" container, an .active class is added and removed from anchor links within the associated navigation. Links must have resolvable id targets, otherwise they're ignored. For example, a <a href="#home">home</a> must correspond to something in the DOM like <div id="home">...</div>.
  • Target elements that are not visible will be ignored. See the non-visible elements section below.

More examples #

The next few sections show more examples of using the scrollspy component with navigation, list group, and simple anchors.

More examples Nested navigation #

Scrollspy also works with nested .nav components. If a nested .nav is .active, its parents will also be .active. Scroll the area next to the navbar and watch the active class change.

Account

Email

Team

Billing

Contact

Support call

HTML
<!-- Nested navigation scrollspy -->
<div class="row">
  <div class="col-sm-4 mb-3 mb-sm-0">
    <nav id="scrollspy-2-navbar" class="flex-column align-items-stretch p-2 border rounded-3">
      <nav class="nav nav-pills flex-column">
        <a class="nav-link" href="#scrollspy-2-account">Account</a>
        <nav class="nav nav-pills flex-column my-1">
          <a class="nav-link ms-2" href="#scrollspy-2-account-email">Email</a>
          <a class="nav-link ms-2" href="#scrollspy-2-account-team">Team</a>
        </nav>
        <a class="nav-link" href="#scrollspy-2-billing">Billing</a>
        <a class="nav-link" href="#scrollspy-2-contact">Contact</a>
        <nav class="nav nav-pills flex-column mt-1">
          <a class="nav-link ms-2" href="#scrollspy-2-contact-support-call">Support call</a>
        </nav>
      </nav>
    </nav>
  </div>
  <div class="col-sm-8">
    <div class="overflow-hidden bg-body-secondary border-docs-demo-container rounded-3" style="height: 204px;">
      <div data-bs-spy="scroll" data-bs-target="#scrollspy-2-navbar" data-bs-smooth-scroll="true" class="h-100 overflow-y-auto" tabindex="0">
        <div id="scrollspy-2-account">
          ...
        </div>
        <div id="scrollspy-2-account-email">
          ...
        </div>
        <div id="scrollspy-2-account-team">
          ...
        </div>
        <div id="scrollspy-2-billing">
          ...
        </div>
        <div id="scrollspy-2-contact">
          ...
        </div>
        <div id="scrollspy-2-contact-support-call">
          ...
        </div>
      </div>
    </div>
  </div>
</div>

More examples List group #

Scrollspy also works with .list-group components. Scroll the area next to the list group and watch the active class change.

Profile

Billing

Messages

Stats

Premium

HTML
<!-- List group scrollspy -->
<div class="row">
  <div class="col-sm-4 mb-3 mb-sm-0">
    <div id="scrollspy-3-list-group" class="list-group">
      <a class="list-group-item list-group-item-action" href="#scrollspy-3-profile">Profile</a>
      <a class="list-group-item list-group-item-action" href="#scrollspy-3-billing">Billing</a>
      <a class="list-group-item list-group-item-action" href="#scrollspy-3-messages">Messages</a>
      <a class="list-group-item list-group-item-action" href="#scrollspy-3-stats">Stats</a>
      <a class="list-group-item list-group-item-action" href="#scrollspy-3-premium">Premium</a>
    </div>
  </div>
  <div class="col-sm-8">
    <div class="overflow-hidden bg-body-secondary border-docs-demo-container rounded-3" style="height: 171px;">
      <div data-bs-spy="scroll" data-bs-target="#scrollspy-3-list-group" data-bs-smooth-scroll="true" class="h-100 overflow-y-auto" tabindex="0">
        <div id="scrollspy-3-profile">
          ...
        </div>
        <div id="scrollspy-3-billing">
          ...
        </div>
        <div id="scrollspy-3-messages">
          ...
        </div>
        <div id="scrollspy-3-stats">
          ...
        </div>
        <div id="scrollspy-3-premium">
          ...
        </div>
      </div>
    </div>
  </div>
</div>

More examples Simple anchors #

Scrollspy is not limited to nav components and list groups, so it will work on any <a> anchor elements in the current document. Scroll the area and watch the .active class change.

Profile

Billing

Messages

Stats

Premium

HTML
<!-- Simple anchors scrollspy -->
<div class="row">
  <div class="col-sm-4 mb-3 mb-sm-0">
    <div id="scrollspy-4-links" class="d-flex flex-column border rounded p-2">
      <a class="p-2 lh-1 rounded" href="#scrollspy-4-profile">Profile</a>
      <a class="p-2 lh-1 rounded" href="#scrollspy-4-billing">Billing</a>
      <a class="p-2 lh-1 rounded" href="#scrollspy-4-messages">Messages</a>
      <a class="p-2 lh-1 rounded" href="#scrollspy-4-stats">Stats</a>
      <a class="p-2 lh-1 rounded" href="#scrollspy-4-premium">Premium</a>
    </div>
  </div>
  <div class="col-sm-8">
    <div class="overflow-hidden bg-body-secondary border-docs-demo-container rounded-3" style="height: 168px;">
      <div data-bs-spy="scroll" data-bs-target="#scrollspy-4-links" data-bs-smooth-scroll="true" class="h-100 overflow-y-auto" tabindex="0">
        <div id="scrollspy-4-profile">
          ...
        </div>
        <div id="scrollspy-4-billing">
          ...
        </div>
        <div id="scrollspy-4-messages">
          ...
        </div>
        <div id="scrollspy-4-stats">
          ...
        </div>
        <div id="scrollspy-4-premium">
          ...
        </div>
      </div>
    </div>
  </div>
</div>
CSS
#scrollspy-4-links > a {
  text-decoration: none;
  color: inherit;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

#scrollspy-4-links > a.active {
  color: var(--bs-primary-foreground);
  background-color: var(--bs-primary);
  -webkit-font-smoothing: auto;
  -moz-osx-font-smoothing: auto;
}

Non-visible elements #

Target elements that aren't visible will be ignored and their corresponding nav items won't receive an .active class. Scrollspy instances initialized in a non-visible wrapper will ignore all target elements. Use the refresh method to check for observable elements once the wrapper becomes visible.

JavaScript
document.querySelectorAll('#nav-tab>[data-bs-toggle="tab"]').forEach((el) => {
  el.addEventListener("shown.bs.tab", () => {
    const target = el.getAttribute("data-bs-target");
    const scrollElem = document.querySelector(
      `${target} [data-bs-spy="scroll"]`
    );
    bootstrap.ScrollSpy.getOrCreateInstance(scrollElem).refresh();
  });
});

JavaScript usage #

The examples on this page use data-bs-* attributes for functionality, which is usually enough to cover a majority of use-cases. You can read about options, methods, events, and more in the official Bootstrap documentation.

Scrollspy - Bootstrap
Continue reading on the offical documentation website

Up next
Sidebar

Help us grow

Our main goal is to make Halfmoon the go-to framework for building websites, dashboards and tools. If you believe in our mission, consider becoming a sponsor and help us grow.

You can email us directly if you have any queries. We are always happy to answer.

Subscribe for updates

We will notify you when the framework gets a substantial update. No spam ever.

Follow us on Twitter so that you can stay updated that way.