Introduction
Scrollspy is a Bootstrap component that automatically updates navigation
links based on the user's scroll position within a page or scrollable container. As the
user scrolls, the corresponding nav link gets the .active class applied to it.
This is especially useful for long documentation pages, single-page layouts, or any content that benefits from contextual navigation. No custom JavaScript is required — Bootstrap handles everything declaratively via data attributes.
Setup
Add data-bs-spy="scroll" to the scrollable element (often <body>
or a scrollable <div>). Point data-bs-target at the selector
for your nav. Add tabindex="0" when the spy target is not <body>.
<!-- On <body> (whole-page scroll) -->
<body
data-bs-spy="scroll"
data-bs-target="#myNav"
data-bs-offset="80"
>
<!-- On a scrollable div -->
<div
data-bs-spy="scroll"
data-bs-target="#myNav"
data-bs-offset="0"
tabindex="0"
style="height: 400px; overflow-y: scroll;"
>
...
</div>Usage
Each nav link's href must match the id of a section inside the
scrollable container. Bootstrap monitors the scroll position and adds .active
to the matching link automatically.
<!-- Nav -->
<nav id="myNav">
<ul class="nav">
<li><a class="nav-link" href="#section1">Section 1</a></li>
<li><a class="nav-link" href="#section2">Section 2</a></li>
<li><a class="nav-link" href="#section3">Section 3</a></li>
</ul>
</nav>
<!-- Sections -->
<div id="section1">Content for section 1</div>
<div id="section2">Content for section 2</div>
<div id="section3">Content for section 3</div>Bootstrap applies .active to the nav link as soon as the corresponding
section reaches the offset threshold from the top of the scroll container.
Options
Scrollspy can be configured via data attributes or JavaScript:
// Via JavaScript
const scrollSpy = new bootstrap.ScrollSpy(document.body, {
target: '#myNav',
offset: 100, // px from top before section activates (default: 10)
method: 'auto' // 'auto' | 'offset' | 'position'
})offset — Number of pixels to offset from top when calculating the active section.
method — offset uses getBoundingClientRect();
position uses offsetTop/offsetLeft;
auto picks based on context.
Events
Bootstrap fires activate.bs.scrollspy on the scroll element every time
a new nav item becomes active. Use event.relatedTarget to get the newly
activated link element.
const scrollEl = document.querySelector('[data-bs-spy="scroll"]')
scrollEl.addEventListener('activate.bs.scrollspy', function (event) {
console.log('Active target:', event.relatedTarget)
// event.relatedTarget → the newly active <a> element
})Live event log (scroll to see it fire):
Refresh
If you dynamically add or remove sections after the page loads, call .refresh()
to recalculate all section offsets. Without a refresh, newly added sections won't be tracked.
// Get the ScrollSpy instance and refresh it
const instance = bootstrap.ScrollSpy.getInstance(
document.querySelector('[data-bs-spy="scroll"]')
)
instance.refresh()
// Or via jQuery-style chaining:
// bootstrap.ScrollSpy.getOrCreateInstance(el).refresh()You should also call refresh() if the container's height changes (e.g. after
an accordion expands), since Bootstrap calculates section thresholds based on element
positions at initialization time.
data-bs-offset
to the navbar's height (e.g. data-bs-offset="56") so sections activate at
the right moment — not while hidden behind the bar.


