On this page
Scrollspy
Highlight the in-page link that matches the section in view. CSS
scroll-target-group and :target-current
replace the ScrollSpy plugin — no JavaScript.
| What runs it | JavaScript | |
|---|---|---|
| Bootstrap 5.3 | Scrollspy plugin | 5.7 KB gzip |
| This repo | scroll-target-group and :target-current | 0 KB |
When that API is missing,
this page also loads scroll-target-group.js (0.8 KB gzip).
See Polyfills.
| Avoid |
|
|---|
scroll-target-group and :target-current
are experimental CSS (Chrome 140+, not native in Firefox or Safari).
This site loads an IntersectionObserver polyfill only when
CSS.supports('scroll-target-group: auto') is false.
Check the
MDN page for scroll-target-group
and the
polyfill notes
before using this in production.
How it works
-
Put in-page links (
href="#id") in a nav, list group, or any group of anchors. Mark that group withdata-polyfill-scrollspy. That setsscroll-target-group: autofor native CSS and is what the polyfill looks for (replacesdata-bs-spy="scroll"/data-bs-target). An optional selector value points at the scroller; otherwise the overflow ancestor of the targets is used. -
Give each section an
idthat matches a link. The scrollable box can be the page or an element with a height andoverflow: auto. -
:target-currentstyles the marker whose target is in view. It supplements.activewith the same look, so either class or the CSS scroll marker can highlight a link. -
A parent
.nav-linkor dropdown toggle stays highlighted with:has(:target-current)when a nested marker is current. -
scroll-behavior: smoothon the scroller replacesdata-bs-smooth-scroll.
Navbar
Scroll the area below the navbar and watch the current link change. Open the dropdown to see its items highlighted too.
[popover] menus use
display: none, so .dropdown-item
fragment links are not scroll markers and the toggle never sees
:target-current. Copy each href into a
.scroll-marker-alias next to the toggle
(aria-hidden="true",
tabindex="-1") so the browser can still
pick a current marker and
:has(:target-current) can style the dropdown as
active. While the menu is open, CSS hides the alias so the
visible item becomes :target-current instead.
First heading
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Second heading
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Third heading
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Fourth heading
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Fifth heading
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
<nav id="navbar-example2" class="navbar bg-body-tertiary px-3 mb-3" data-polyfill-scrollspy>
<a class="navbar-brand" href="#">Navbar</a>
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="#scrollspyHeading1">First</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#scrollspyHeading2">Second</a>
</li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" command="toggle-popover" commandfor="scrollspyDropdown">Dropdown</button>
<ul class="dropdown-menu" popover="auto" id="scrollspyDropdown">
<li><a class="dropdown-item" href="#scrollspyHeading3">Third</a></li>
<li><a class="dropdown-item" href="#scrollspyHeading4">Fourth</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#scrollspyHeading5">Fifth</a></li>
</ul>
<span class="scroll-marker-alias" aria-hidden="true">
<a href="#scrollspyHeading3" tabindex="-1">Third</a>
<a href="#scrollspyHeading4" tabindex="-1">Fourth</a>
<a href="#scrollspyHeading5" tabindex="-1">Fifth</a>
</span>
</li>
</ul>
</nav>
<div class="scrollspy-example bg-body-tertiary p-3 rounded-2" tabindex="0">
<h4 id="scrollspyHeading1">First heading</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="scrollspyHeading2">Second heading</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="scrollspyHeading3">Third heading</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="scrollspyHeading4">Fourth heading</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="scrollspyHeading5">Fifth heading</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
</div> Nested nav
Scrollspy also works with nested .navs. If a nested link is current, its parent nav-link is highlighted as well.
Item 1
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.
Item 1-1
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.
Item 1-2
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.
Item 2
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.
Item 3
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.
Item 3-1
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.
Item 3-2
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.
<div class="row">
<div class="col-4">
<nav id="navbar-example3" class="h-100 flex-column align-items-stretch pe-4 border-end">
<nav class="nav nav-pills flex-column" data-polyfill-scrollspy>
<a class="nav-link" href="#item-1">Item 1</a>
<nav class="nav nav-pills flex-column">
<a class="nav-link ms-3 my-1" href="#item-1-1">Item 1-1</a>
<a class="nav-link ms-3 my-1" href="#item-1-2">Item 1-2</a>
</nav>
<a class="nav-link" href="#item-2">Item 2</a>
<a class="nav-link" href="#item-3">Item 3</a>
<nav class="nav nav-pills flex-column">
<a class="nav-link ms-3 my-1" href="#item-3-1">Item 3-1</a>
<a class="nav-link ms-3 my-1" href="#item-3-2">Item 3-2</a>
</nav>
</nav>
</nav>
</div>
<div class="col-8">
<div class="scrollspy-example-2" tabindex="0">
<div id="item-1">
<h4>Item 1</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<p>Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.</p>
</div>
<div id="item-1-1">
<h5>Item 1-1</h5>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<p>Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.</p>
</div>
<div id="item-1-2">
<h5>Item 1-2</h5>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<p>Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.</p>
</div>
<div id="item-2">
<h4>Item 2</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<p>Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.</p>
</div>
<div id="item-3">
<h4>Item 3</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<p>Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.</p>
</div>
<div id="item-3-1">
<h5>Item 3-1</h5>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<p>Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.</p>
</div>
<div id="item-3-2">
<h5>Item 3-2</h5>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<p>Keep in mind that the browser picks one current scroll marker. Multiple visible targets at the same time may still look busy.</p>
</div>
</div>
</div>
</div> List group
Scrollspy also works with .list-groups. Scroll the area next to the list group and watch the current item change.
Item 1
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Item 2
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Item 3
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Item 4
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
<div class="row">
<div class="col-4">
<div id="list-example" class="list-group" data-polyfill-scrollspy>
<a class="list-group-item list-group-item-action" href="#list-item-1">Item 1</a>
<a class="list-group-item list-group-item-action" href="#list-item-2">Item 2</a>
<a class="list-group-item list-group-item-action" href="#list-item-3">Item 3</a>
<a class="list-group-item list-group-item-action" href="#list-item-4">Item 4</a>
</div>
</div>
<div class="col-8">
<div class="scrollspy-example" tabindex="0">
<h4 id="list-item-1">Item 1</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="list-item-2">Item 2</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="list-item-3">Item 3</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="list-item-4">Item 4</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
</div>
</div>
</div> Simple anchors
Scrollspy is not limited to nav components and list groups. Any fragment link in a data-polyfill-scrollspy group works.
Item 1
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Item 2
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Item 3
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Item 4
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
Item 5
This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.
<div class="row">
<div class="col-4">
<div id="simple-list-example" class="d-flex flex-column gap-2 simple-list-example-scrollspy text-center" data-polyfill-scrollspy>
<a class="p-1 rounded" href="#simple-list-item-1">Item 1</a>
<a class="p-1 rounded" href="#simple-list-item-2">Item 2</a>
<a class="p-1 rounded" href="#simple-list-item-3">Item 3</a>
<a class="p-1 rounded" href="#simple-list-item-4">Item 4</a>
<a class="p-1 rounded" href="#simple-list-item-5">Item 5</a>
</div>
</div>
<div class="col-8">
<div class="scrollspy-example" tabindex="0">
<h4 id="simple-list-item-1">Item 1</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="simple-list-item-2">Item 2</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="simple-list-item-3">Item 3</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="simple-list-item-4">Item 4</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
<h4 id="simple-list-item-5">Item 5</h4>
<p>This is some placeholder content for the scrollspy page. As the area scrolls, the matching navigation link is highlighted. The same paragraph is repeated so the scroller is long enough to move the highlight from heading to heading.</p>
</div>
</div>
</div> Accessibility
:target-current is visual only. Assistive technology still needs aria-current="true" on the in-view link, which requires script. This demo does not set it. The links remain ordinary in-page links.
Browser support
| No script | Polyfill | No fallback |
|---|---|---|
scroll-target-group: Chrome 140+. | IntersectionObserver when the CSS is missing. It does not set aria-current. | Marked experimental on the component page. The polyfill covers browsers without the CSS. |