Scroll Progress Bar for React

Fixed bar at the top that scales horizontally with scroll progress. Uses Framer Motion for smooth updates.

Component

Installation

 

Usage

Import

Add the ScrollProgress import.

import { ScrollProgress } from "@/components/scroll-progress";

Use

Add to your layout (e.g. root layout).

<ScrollProgress />;

Guidelines

  • Place in your root layout so it tracks page scroll, or pass containerRef for a custom scroll container.
  • Use inline with containerRef when the bar should appear inside the scroll container (parent needs relative).
  • Use className to customize height, color, or position.
  • The bar scales from left to right; ensure sufficient scroll height to see the effect.

Props

All props are optional unless marked required. Use these to customize every aspect of the component.

PropTypeDefaultDescription
classNamestring-Additional CSS classes for the bar (e.g. height, color).
containerRefReact.RefObject<HTMLElement | null>undefinedRef to a scrollable overflow container. Omit to track window scroll instead.
inlinebooleanfalseWhen true, positions the bar absolutely inside its parent (use with relative container) instead of fixed to viewport.

Accessibility

  • Decorative visual indicator only; the bar is marked aria-hidden true and exposes no role, label, or accessible name to assistive technology.
  • There are no interactive controls, buttons, links, or focusable elements, so keyboard operability and focus visibility do not apply to this component.
  • Scroll position is already conveyed natively by the browser scrollbar, so screen reader and keyboard users lose no information when this purely visual layer is ignored.
  • It does not honour prefers-reduced-motion: the spring-driven scaleX always animates, so it should be guarded with a matchMedia('(prefers-reduced-motion: reduce)') check to disable the spring for users who prefer reduced motion.

Performance

  • Animates only scaleX (a transform) and opacity, both of which are GPU compositable and avoid layout or paint, making each scroll update cheap.
  • The scroll listener is registered with passive true so it never blocks the scroll thread, and it is removed in the useEffect cleanup to prevent leaks.
  • Uses Framer Motion useMotionValue and useSpring to smooth updates outside of React render, so scrolling does not trigger component re-renders.
  • A single requestAnimationFrame gates the initial opacity reveal, and no IntersectionObserver, will-change, or timers are used, keeping the footprint minimal.
  • Motion is never gated by reduced-motion or visibility, so the spring keeps animating on every scroll even for users who would prefer it disabled.

Examples

Basic

Add to your layout for a global scroll indicator.

import { ScrollProgress } from "@/components/scroll-progress";

export function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      <ScrollProgress />
      {children}
    </>
  );
}

Custom styling

Customize height and color with className.

import { ScrollProgress } from "@/components/scroll-progress";

<ScrollProgress className="h-0.5 bg-violet-500" />;

With container ref

Track scroll inside an overflow container. Use inline so the bar stays at top of the container.

import { useRef } from "react";
import { ScrollProgress } from "@/components/scroll-progress";

export function ScrollableSection() {
  const containerRef = useRef<HTMLDivElement>(null);

  return (
    <div
      ref={containerRef}
      className="relative h-96 overflow-y-auto"
    >
      <ScrollProgress containerRef={containerRef} inline />
      <div className="p-4">{/* content */}</div>
    </div>
  );
}

Last updated on Jun 25

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: