A Table of Contents (TOC) is essential for long-form technical blogs. It helps readers navigate between different sections seamlessly. In this guide, we will implement an active-tracking TOC in Next.js using the new CSS Anchor Positioning API and React hooks.
What is CSS Anchor Positioning?
CSS Anchor Positioning is a game-changing API that allows you to position one element relative to another anchor element anywhere on the page without JavaScript coordinate calculations. We will use it to anchor a floating marker next to the active heading link in our sidebar.
Step 1: Building the TOC Component
First, let's set up the React component that parses headings and tracks which one is currently in view using the Intersection Observer API.
'use client';
import { useEffect, useState } from 'react';
export function TableOfContents({ headings }) {
const [activeId, setActiveId] = useState('');
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
const visibleEntry = entries.find((entry) => entry.isIntersecting);
if (visibleEntry) {
setActiveId(visibleEntry.target.id);
}
},
{ rootMargin: '-20% 0px -60% 0px' }
);
headings.forEach((h) => {
const el = document.getElementById(h.id);
if (el) observer.observe(el);
});
return () => observer.disconnect();
}, [headings]);
return (
<nav className="toc-navigation">
<div className="active-indicator" style={{ anchorName: '--active-marker' }} />
<ul>
{headings.map((h) => (
<li key={h.id}>
<a
href={`#${h.id}`}
className={activeId === h.id ? 'active' : ''}
style={activeId === h.id ? { anchorDefault: '--active-marker' } : {}}
>
{h.text}
</a>
</li>
))}
</ul>
</nav>
);
}Step 2: Styling the Floating Indicator
We style the floating indicator and apply the anchor positions in CSS. When the active link changes, the anchor repositioning does the work, animating the dot smoothly down the sidebar list.
.toc-navigation {
position: sticky;
top: 100px;
padding-left: 20px;
border-left: 2px solid #e2e8f0;
}
.active-indicator {
position: absolute;
width: 4px;
height: 20px;
background-color: #3b82f6;
left: -2px;
top: anchor(top);
transition: top 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.toc-navigation a.active {
color: #3b82f6;
font-weight: 500;
}This approach minimizes paint loops and layout recalculations, delivering a buttery-smooth UX on desktops and tablets.
