Next.js

A Modern Table of Contents in Next.js with CSS Anchor Positioning

In this tutorial, you'll learn how to build a floating table of contents component for your Next.js blog that tracks the reader's scroll position and highlights the active section with a smooth animated dot.

Vikas PrasadVikas Prasad
6 min read
✏️ Next.js

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.

Vikas Prasad

Written by Vikas Prasad

I'm a software developer and technical writer specializing in modern JavaScript, React, Angular, and performant web architecture. I share build instructions and deep dives into technology standardizations.