Bootstrap Scrollspy
Automatically update navigation links based on scroll position. The sidebar navigation highlights the current section as you scroll through the content.
info How Scrollspy Works
Standard Implementation: In a typical website, the <body> element is the scrolling container. Bootstrap Scrollspy listens to scroll events on the body and automatically highlights the corresponding navigation links based on the current viewport position.
Admin Dashboard Implementation: In the UltraViolet admin layout, the <body> doesn't scroll. Instead, the .ultraviolet-content div is the scrolling element. This is a common pattern in fixed-layout dashboards with sticky headers and sidebars.
The Solution: According to the Bootstrap documentation, Scrollspy can work with any scrollable container by:
- Creating a dedicated scrollable div with
overflow-y: autoand amax-height - Adding the scrollspy data attributes to that specific div (not the body)
- Keeping the navigation sidebar outside the scrolling container with
position: sticky
Key Insight: Scrollspy must be applied to the actual
scrolling element. In this page, that's the div you're currently scrolling in (with max-height: calc(100vh - 200px)), not the page body.
Standard Scrollspy Implementation
For typical websites where the <body> element scrolls, the standard Bootstrap Scrollspy implementation is straightforward:
HTML Structure - Standard Body Scroll
<body data-bs-spy="scroll" data-bs-target="#navbar" data-bs-smooth-scroll="true" tabindex="0">
<nav id="navbar" class="navbar">
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="#section1">Section 1</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#section2">Section 2</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#section3">Section 3</a>
</li>
</ul>
</nav>
<div class="container">
<section id="section1">
<h2>Section 1 Content</h2>
<p>Your content here...</p>
</section>
<section id="section2">
<h2>Section 2 Content</h2>
<p>Your content here...</p>
</section>
<section id="section3">
<h2>Section 3 Content</h2>
<p>Your content here...</p>
</section>
</div>
</body>
Admin Dashboard Implementation
In the UltraViolet admin dashboard, we need a different approach because the layout uses a fixed header and the body doesn't scroll. Here's how to implement scrollspy in this environment:
HTML Structure - Custom Scrollable Container
@extends("layouts.admin")
@section("content")
<div class="row">
<!-- Main Content Column with Scrollspy -->
<div class="col-md-9">
<div data-bs-spy="scroll"
data-bs-target="#tableOfContents"
data-bs-smooth-scroll="true"
class="scrollspy-example"
tabindex="0"
style="max-height: calc(100vh - 200px); overflow-y: auto; position: relative;">
<section id="introduction">
<h2>Introduction</h2>
<p>Your content here...</p>
</section>
<section id="features">
<h2>Features</h2>
<p>Your content here...</p>
</section>
</div>
</div>
<!-- Sticky Sidebar Navigation -->
<div class="col-md-3">
<nav id="tableOfContents" class="nav nav-pills flex-column sticky-sidebar">
<a class="nav-link" href="#introduction">Introduction</a>
<a class="nav-link" href="#features">Features</a>
</nav>
</div>
</div>
@endsection
Required CSS Styles
/* Make the sticky sidebar actually stick */
.sticky-sidebar {
position: sticky;
top: 20px;
align-self: flex-start;
}
/* Style the active link */
.nav-pills .nav-link.active {
background-color: #0d6efd;
color: white;
}
.nav-pills .nav-link {
color: var(--bs-body-color);
}
.nav-pills .nav-link:hover {
background-color: rgba(13, 110, 253, 0.1);
}
/* Add spacing between sections for better scroll tracking */
section {
padding-top: 50px;
padding-bottom: 50px;
}
- The scrollspy attributes are on the scrollable div, not the body
- The sidebar is outside the scrolling container
- Use
max-height: calc(100vh - 200px)to fill viewport minus header space - The
tabindex="0"attribute makes the div keyboard-accessible
JavaScript Initialization (Optional)
While the data attributes are sufficient for most use cases, you can also initialize scrollspy programmatically with JavaScript for more control:
JavaScript Initialization
// Initialize scrollspy programmatically
document.addEventListener('DOMContentLoaded', function() {
const scrollSpyElement = document.querySelector('[data-bs-spy="scroll"]');
if (scrollSpyElement && typeof bootstrap !== 'undefined') {
const scrollSpy = new bootstrap.ScrollSpy(scrollSpyElement, {
target: '#tableOfContents',
smoothScroll: true,
rootMargin: '0px 0px -40%', // Trigger earlier/later
threshold: 0.5 // How much of element must be visible
});
}
});
Scrollspy Options
Available Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
target
|
string | Element | null | Specifies element to apply scrollspy to (usually your nav) |
rootMargin
|
string | '0px 0px -25%' | Offset for intersection observer (when to trigger active state) |
smoothScroll
|
boolean | false | Enables smooth scrolling when clicking nav links |
threshold
|
number | array | [0.1, 0.5, 1] | Intersection observer threshold values |
Nested Navigation
You can also create nested navigation for sub-sections:
Nested Nav Example
<nav id="tableOfContents" class="nav nav-pills flex-column sticky-sidebar">
<a class="nav-link" href="#item-1">Item 1</a>
<a class="nav-link" href="#item-2">Item 2</a>
<a class="nav-link" href="#item-3">Item 3</a>
<!-- Nested sub-navigation -->
<nav class="nav nav-pills flex-column ps-3">
<a class="nav-link" href="#item-3-1">Sub-item 3.1</a>
<a class="nav-link" href="#item-3-2">Sub-item 3.2</a>
</nav>
<a class="nav-link" href="#item-4">Item 4</a>
</nav>
Best Practices & Troubleshooting
Follow these guidelines to ensure your scrollspy implementation works correctly in the admin dashboard:
tips_and_updates Best Practices
- Section IDs: Ensure all sections have unique
idattributes that match thehrefvalues in your nav links - Section Spacing: Add adequate padding to sections (e.g.,
padding-top: 50px) to ensure proper scroll tracking - Sidebar Position: Keep the sidebar outside the scrolling container for sticky behavior to work
- Viewport Height: Use
calc(100vh - XXXpx)to account for headers, breadcrumbs, and padding - Accessibility: Always include
tabindex="0"on scrollable divs for keyboard navigation
bug_report Common Issues & Solutions
Solution: Ensure the sidebar is outside the scrolling div and has
position: sticky with align-self: flex-start in the CSS.Solution: Make sure scrollspy attributes are on the actual scrolling element, not the body. In admin dashboard, this is the div with
overflow-y: auto.Solution: Use
max-height instead of fixed height, and adjust the calculation (e.g., calc(100vh -
200px)) to account for header/breadcrumb space.Solution: Ensure your nav links use proper anchor tags with
href="#section-id" format, and include data-bs-smooth-scroll="true" for smooth scrolling.