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 verifiedUses 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.jsonMethod 02 · Copy and paste
- 1. Install the dependencies below.
- 2. Copy the source from the Code tab into
components/ui/scroll-progress-navbar.tsx. - 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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
| brand | ReactNode | — | Logo or wordmark on the left. |
| links | NavbarLink[] | [] | { label: string; href: string; active?: boolean }. Collapse into a menu below a 36rem container width. |
| action | ReactNode | — | Slot on the right, e.g. a call-to-action. |
| container | RefObject<HTMLElement | null> | — | Scrollable element to track. Defaults to the window. |
| threshold | number | 48 | Scroll distance in px after which the bar turns compact. |
| sticky | boolean | true | Stick to the top of the scrolling area. |
| showProgress | boolean | true | Show the reading progress bar. |
| progressPosition | "top" | "bottom" | "bottom" | Edge the progress bar sits on. |
| smooth | boolean | true | Spring-smooth the progress bar. |
| accent | string | "#FF7448" | Progress, active link and focus ring colour. |
| label | string | "Main" | Accessible name of the navigation landmark. |
| ...props | HTMLAttributes | — | 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
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.