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!
Navbar example #
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.
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 setheight
andoverflow-y: scroll
. - On the scrollable container, add
data-bs-spy="scroll"
anddata-bs-target="#{id}"
where{id}
is the uniqueid
of the associated navigation. If there is no focusable element inside the element, be sure to also include atabindex="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 resolvableid
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
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.
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.
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 #
halfmoon.js
Halfmoon is a drop-in replacement for Bootstrap. We implement no JavaScript on our own, therefore, there is no halfmoon.js
. Instead we rely entirely on bootstrap.bundle.js
(which can be downloaded from Bootstrap's website). Read the JavaScript section on our download page to learn more. It also contains a starter template to help you get started quickly.
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
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.