Scroll Progress

Fixed bar that tracks reading progress down the page.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/scroll-progress.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

"use client"; import * as React from "react"; import { cn } from "../../lib/utils"; /** Props for {@link ScrollProgress}. */ export type ScrollProgressProps = React.ComponentPropsWithoutRef<"div">; function computeProgress(): number { const element = document.documentElement; const scrollable = element.scrollHeight - element.clientHeight; if (scrollable <= 0) { return 0; } return (element.scrollTop / scrollable) * 100; } /** * Fixed bar pinned to the top of the page that grows with scroll progress. * * Functional under `prefers-reduced-motion`: it updates instantly. * * @example * ```tsx * <ScrollProgress /> * ``` */ export const ScrollProgress = ({ className, ref, style, ...props }: ScrollProgressProps & { ref?: React.Ref<HTMLDivElement> }) => { const [progress, setProgress] = React.useState(0); React.useEffect(() => { const onScroll = (): void => { setProgress(computeProgress()); }; onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); return () => { window.removeEventListener("scroll", onScroll); }; }, []); return ( <div aria-valuemax={100} aria-valuemin={0} aria-valuenow={Math.round(progress)} className={cn( "fixed inset-x-0 top-0 z-50 h-1 origin-left bg-primary", className, )} ref={ref} role="progressbar" style={{ width: `${progress}%`, ...style }} {...props} /> ); }; ScrollProgress.displayName = "ScrollProgress";

Dependencies

  • @vllnt/ui@^0.2.1