Skip to component
GGamma

Section navigator

A floating section list that follows the active part of a scrollable document.

New
01 / 04

Overview

A focused introduction to the page.

02 / 04

Principles

The decisions guiding this interface.

03 / 04

Details

A closer look at the small interactions.

04 / 04

Next steps

Where to go after this section.

Live previewUse keyboard to interact

Implementation

1"use client";2 3import { useEffect, useState, type RefObject } from "react";4import { ChevronDown, List } from "lucide-react";5 6export type SectionNavigatorItem = { id: string; title: string };7export type SectionNavigatorProps = {8  sections: SectionNavigatorItem[];9  scrollContainer: RefObject<HTMLElement | null>;10};11 12/** A compact section picker that tracks headings inside a scroll container. */13export function SectionNavigator({14  sections,15  scrollContainer,16}: SectionNavigatorProps) {17  const [active, setActive] = useState(sections[0]?.id ?? "");18  const [open, setOpen] = useState(false);19 20  useEffect(() => {21    const root = scrollContainer.current;22    if (!root || !sections.length) return;23    const observer = new IntersectionObserver(24      (entries) => {25        const visible = entries26          .filter((entry) => entry.isIntersecting)27          .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);28        if (visible[0]) setActive(visible[0].target.id);