Skip to content

Chapter Progress Nav

A sticky section nav whose items fill like progress bars as you read — and still reaches the short last section that fixed-line navs never activate.

DOM + CSS0 deps1 file · 9 KB TSXreduced-motion aware

ProEditorialeditorialminimal
Preview

Props

Numbered

The source is part of Pro

$27/month or $179 once. See Pro

No runtime dependencies
"use client";

import { useEffect, useRef, useState } from "react";

/**
 * A sticky section nav whose items fill like progress bars as you read through
 * each section — done sections stay full, the current one fills live, the rest
 * wait empty.
 *
 * THE READING LINE SLIDES DOWN OVER THE LAST SCREENFUL. A nav that activates
 * sections when they cross a fixed line under the header never activates the
 * last one or two: a short final section cannot scroll up to that line, so the
 * nav sits on the second-to-last item at the bottom of the page. Here the line
 * starts just under the nav and, during the final viewport-height of scrolling,
 * glides down to the bottom edge — so at the end of the page every section
 * reads as complete and the last one is active.
 *
 * THE BARS NEVER GO THROUGH REACT. A scroll handler that sets state re-renders

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Details

About this component

Long single-page sites — a restaurant menu, a list of services, a price sheet — need a way to show where the reader is and to jump between sections, and the usual scrollspy does half of it. It highlights whichever section has crossed a fixed line under the header, which fails at the end of every page: a short final section can never scroll up to that line, so the nav stays on the second-to-last item while the reader looks at the last. This nav slides its reading line down to the bottom of the viewport over the final screenful of scrolling, so every section completes and the last one activates. Each item carries a track that fills with the reader's progress through its section, which turns the nav into a quiet table of contents with a sense of length. The fills are written straight to the DOM from one throttled scroll handler — only the active index passes through React — so the nav costs nothing while scrolling. On narrow screens the nav scrolls itself horizontally to keep the active item in view without touching the page's scroll, and a click moves focus into the section it lands on.

Curator’s note

Every scrollspy I have audited activates on a fixed line under the header, so the last section of any page with a short footer is unreachable. Sliding the line down over the final screen is a one-line fix.

Pairs well with

Trending
Pro