Scroll Progress
Fixed bar at the top that scales horizontally with scroll progress. Uses Framer Motion for smooth updates.
Component
Scroll down to see the progress bar fill.
Section 1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 3
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 4
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 5
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 6
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 7
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 8
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 9
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 10
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 11
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 12
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
"use client";
import { motion, useMotionValue } from "framer-motion";
import { useEffect } from "react";
import { cn } from "@/lib/utils";
interface ScrollProgressProps {
className?: string;
containerRef?: React.RefObject<HTMLElement | null>;
inline?: boolean;
}
export function ScrollProgress({
className,
containerRef,
inline,
}: ScrollProgressProps) {
const scrollProgress = useMotionValue(0);
useEffect(() => {
const scrollEl: HTMLElement | Window =
containerRef?.current ?? window;
const handleScroll = () => {
if (containerRef?.current) {
const el = containerRef.current;
const scrollTop = el.scrollTop;
const docHeight = el.scrollHeight - el.clientHeight;
const progress =
docHeight > 0 ? scrollTop / docHeight : 0;
scrollProgress.set(progress);
} else {
const scrollTop = window.scrollY;
const docHeight =
document.documentElement.scrollHeight -
window.innerHeight;
const progress =
docHeight > 0 ? scrollTop / docHeight : 0;
scrollProgress.set(progress);
}
};
const handleScrollReset = () => {
setTimeout(handleScroll, 0);
};
scrollEl.addEventListener("scroll", handleScroll, {
passive: true,
});
handleScroll();
handleScrollReset();
return () =>
scrollEl.removeEventListener("scroll", handleScroll);
}, [containerRef, scrollProgress]);
const bar = (
<motion.div
className="h-full w-full bg-primary"
style={{
scaleX: scrollProgress,
transformOrigin: "left",
}}
aria-hidden="true"
/>
);
if (inline) {
return (
<div
className={cn(
"sticky top-0 z-10 h-1 w-full overflow-hidden rounded-t-lg bg-muted/50",
className,
)}
>
{bar}
</div>
);
}
return (
<motion.div
className={cn(
"fixed top-0 left-0 z-50 h-1 w-full bg-primary",
className,
)}
style={{
scaleX: scrollProgress,
transformOrigin: "left",
}}
aria-hidden="true"
/>
);
}Installation
pnpm dlx shadcn@latest add "https://pulkitxm.com/components/scroll-progress.json"1. Install dependencies
pnpm add framer-motion2. Copy the component file
"use client";
import { motion, useMotionValue } from "framer-motion";
import { useEffect } from "react";
import { cn } from "@/lib/utils";
interface ScrollProgressProps {
className?: string;
containerRef?: React.RefObject<HTMLElement | null>;
inline?: boolean;
}
export function ScrollProgress({
className,
containerRef,
inline,
}: ScrollProgressProps) {
const scrollProgress = useMotionValue(0);
useEffect(() => {
const scrollEl: HTMLElement | Window =
containerRef?.current ?? window;
const handleScroll = () => {
if (containerRef?.current) {
const el = containerRef.current;
const scrollTop = el.scrollTop;
const docHeight = el.scrollHeight - el.clientHeight;
const progress =
docHeight > 0 ? scrollTop / docHeight : 0;
scrollProgress.set(progress);
} else {
const scrollTop = window.scrollY;
const docHeight =
document.documentElement.scrollHeight -
window.innerHeight;
const progress =
docHeight > 0 ? scrollTop / docHeight : 0;
scrollProgress.set(progress);
}
};
const handleScrollReset = () => {
setTimeout(handleScroll, 0);
};
scrollEl.addEventListener("scroll", handleScroll, {
passive: true,
});
handleScroll();
handleScrollReset();
return () =>
scrollEl.removeEventListener("scroll", handleScroll);
}, [containerRef, scrollProgress]);
const bar = (
<motion.div
className="h-full w-full bg-primary"
style={{
scaleX: scrollProgress,
transformOrigin: "left",
}}
aria-hidden="true"
/>
);
if (inline) {
return (
<div
className={cn(
"sticky top-0 z-10 h-1 w-full overflow-hidden rounded-t-lg bg-muted/50",
className,
)}
>
{bar}
</div>
);
}
return (
<motion.div
className={cn(
"fixed top-0 left-0 z-50 h-1 w-full bg-primary",
className,
)}
style={{
scaleX: scrollProgress,
transformOrigin: "left",
}}
aria-hidden="true"
/>
);
}3. Import and use
import { ScrollProgress } from "@/components/scroll-progress";
<ScrollProgress />;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.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes for the bar (e.g. height, color). |
| containerRef | React.RefObject<HTMLElement | null> | undefined | Ref to a scrollable overflow container. Omit to track window scroll instead. |
| inline | boolean | false | When true, positions the bar absolutely inside its parent (use with relative container) instead of fixed to viewport. |
Examples
Basic
Add to your layout for a global scroll indicator.
Scroll down to see the progress bar fill.
Section 1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 3
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 4
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 5
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 6
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 7
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 8
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 9
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 10
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 11
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 12
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
import { ScrollProgress } from "@/components/scroll-progress";
export function Layout({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
<ScrollProgress />
{children}
</>
);
}Custom styling
Customize height and color with className.
Scroll down to see the progress bar fill.
Section 1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 3
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 4
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 5
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 6
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 7
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 8
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 9
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 10
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 11
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 12
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
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.
Scroll down to see the progress bar fill.
Section 1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 2
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 3
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 4
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 5
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 6
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 7
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 8
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 9
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 10
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 11
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
Section 12
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.
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>
);
}