Skip to content
WANCRAFT

D.16Navigation & Menus

Scroll Progress Navbar

A header that tightens and gains a surface as you scroll, with a reading progress bar.

ReactMotionTailwindintermediate

Overview

Scroll Progress Navbar starts as an airy, transparent bar and, once you scroll past a threshold, compresses its height and padding and gains a surface, hairline border and soft shadow. A thin accent bar tracks reading progress, optionally spring-smoothed. Scroll position is read with motion's useScroll and written straight to data attributes, so scrolling never re-renders React. It tracks the window by default or any scrollable container passed by ref, and collapses its links into an accessible disclosure menu on narrow containers.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/scroll-progress-navbar.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/scroll-progress-navbar.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/scroll-progress-navbar.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
1 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { ScrollProgressNavbar, type NavbarLink } from "@/components/ui/scroll-progress-navbar";

export function ScrollProgressNavbarExample() {
  const links: NavbarLink[] = [
    { label: "Essays", href: "/essays", active: true },
    { label: "Archive", href: "/archive" },
    { label: "About", href: "/about" },
  ];

  return (
    <ScrollProgressNavbar
      brand={<span className="font-semibold">Field Notes</span>}
      links={links}
    />
  );
}

03Props

PropTypeDefaultDescription
brandReactNode—Logo or wordmark on the left.
linksNavbarLink[][]{ label: string; href: string; active?: boolean }. Collapse into a menu below a 36rem container width.
actionReactNode—Slot on the right, e.g. a call-to-action.
containerRefObject<HTMLElement | null>—Scrollable element to track. Defaults to the window.
thresholdnumber48Scroll distance in px after which the bar turns compact.
stickybooleantrueStick to the top of the scrolling area.
showProgressbooleantrueShow the reading progress bar.
progressPosition"top" | "bottom""bottom"Edge the progress bar sits on.
smoothbooleantrueSpring-smooth the progress bar.
accentstring"#FF7448"Progress, active link and focus ring colour.
labelstring"Main"Accessible name of the navigation landmark.
...propsHTMLAttributes—Native attributes are forwarded to the <header>.

04Features

  • 01Compact transition (height, padding, surface, border, shadow) after a threshold
  • 02Reading progress bar with optional spring smoothing
  • 03Window or any scrollable container via ref
  • 04No React re-renders while scrolling — state is written to data attributes
  • 05Container-query responsive: links collapse into a disclosure menu on narrow widths
motion · scroll-linkedmotion · tweeninput · scrollinput · clickinput · keyboard

05Accessibility

  • Semantic <header> with a labelled <nav> landmark; the active link has aria-current="page".
  • The progress bar is a labelled role="progressbar" whose aria-valuenow follows the scroll position.
  • The mobile menu button exposes aria-expanded / aria-controls and the panel uses the hidden attribute.
  • Transitions are removed and progress is unsmoothed with prefers-reduced-motion.