toc
Highlights the current section in a table of contents as you scroll.
To see this controller in action, visit Getting Started on desktop. The "On this page" sidebar highlights the current section as you scroll.
Usage
<nav data-controller="toc" data-toc-offset-value="100">
<a href="#section-1" data-toc-target="link" data-id="section-1">
Section 1
</a>
<a href="#section-2" data-toc-target="link" data-id="section-2">
Section 2
</a>
</nav>
Targets
| Target | Purpose |
|---|---|
link |
TOC links that get highlighted |
Values
| Value | Type | Default | Description |
|---|---|---|---|
offset |
Number | 100 |
Offset from top for activation |
Required CSS
[data-toc-target="link"] {
@apply text-gray-600 hover:text-gray-900 transition-colors;
}
[data-toc-target="link"].active {
@apply text-blue-600 font-medium border-blue-500;
}
Source
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["link"];
static values = {
offset: { type: Number, default: 100 },
};
connect() {
this.headings = [];
this.collectHeadings();
if (this.headings.length > 0) {
this.boundScrollHandler = this.onScroll.bind(this);
window.addEventListener("scroll", this.boundScrollHandler, {
passive: true
});
this.highlightCurrentSection();
}
}
disconnect() {
if (this.boundScrollHandler) {
window.removeEventListener("scroll", this.boundScrollHandler);
}
}
collectHeadings() {
this.linkTargets.forEach((link) => {
const id = link.dataset.id || link.getAttribute("href")?.replace("#", "");
const heading = document.getElementById(id);
if (heading) {
this.headings.push({ id, element: heading, link });
}
});
}
onScroll() {
requestAnimationFrame(() => this.highlightCurrentSection());
}
highlightCurrentSection() {
const scrollPos = window.scrollY + this.offsetValue;
let currentHeading = null;
for (let i = this.headings.length - 1; i >= 0; i--) {
if (this.headings[i].element.offsetTop <= scrollPos) {
currentHeading = this.headings[i];
break;
}
}
this.linkTargets.forEach((link) => link.classList.remove("active"));
if (currentHeading) {
currentHeading.link.classList.add("active");
}
}
}
Auto-Scroll TOC
If the TOC container is scrollable, the active item scrolls into view automatically.