Skip to content
Template Empire / components

Search components

10 results

Browse components

Scroll Progress

A free reading progress bar that fills a scroll section indicator as you read.

  • Automated axe check
  • Reduced-motion tested
  • SSR build verified
  • GSAP
  • 2 runtime packages

Scroll Progress is a free reading progress bar for Next.js and React: a fixed bar across the top of the viewport whose fill scales from empty to full as the page scrolls, driven by a single GSAP ScrollTrigger scrub and styled with Tailwind. By default it tracks the whole document; pass a target selector and it maps its range to one article instead.

The fill is one scaleX transform scrubbed directly from native scroll, so it stays exactly in step with the scrollbar at any speed and never runs its own animation loop. The bar ignores the pointer, is hidden from assistive technology as pure decoration, and with prefers-reduced-motion enabled no scroll-linked animation is created at all: the fill is hidden and the track renders static.

Reading progress

Look at the top edge of the viewport

The bar you can see fixed across the very top of this page is the demo. As you scroll down, its fill scales from empty to full, mapped exactly to how far through the document you are: empty at the top, full when you reach the bottom.

There is nothing to configure to get this behaviour. Drop the component anywhere on a page and it tracks the document scroll by default, driven by a single GSAP ScrollTrigger scrub on one scaleX transform, so it stays perfectly in step with the scrollbar at any speed.

Long reads are where a progress bar earns its place. On an article or changelog it answers the one question every reader silently asks: how much is left? Pass a target selector and the bar maps its range to that single article instead of the whole page.

The bar itself stays out of the way. It is a few pixels tall, ignores the pointer entirely and is hidden from assistive technology, because it repeats information the scrollbar already conveys rather than adding anything new.

With reduced motion enabled the fill is hidden and no scroll-linked animation is created at all; the track renders static and empty. Keep scrolling to the end of this page and watch the fill arrive at exactly one hundred percent as you do.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/scroll-progress

Usage

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

export default function Page() {
  return (
    <>
      <ScrollProgress target="#article" />
      <article id="article">
        <h1>The long read</h1>
        <p>The bar fills as this article scrolls through the viewport.</p>
      </article>
    </>
  );
}

Props

PropTypeDefaultDescription
targetstringundefinedCSS selector of the article to track. The fill maps that element's scroll range to 0 to 100%. Tracks the whole document when unset.
classNamestringundefinedAdditional classes merged onto the fixed bar container.

Customisation

Scroll Progress declares two component tokens: --progress-color sets the fill colour and defaults to the brand token, and --progress-height sets the bar height and defaults to 3px. Override either from :root, or from any ancestor of the bar, without touching the component:

:root {
  --progress-color: var(--color-accent-400);
  --progress-height: 5px;
}

Frequently asked questions

Is Scroll Progress free for commercial use?
Yes. Scroll Progress is MIT licensed, so you can use it in personal and commercial projects without attribution.
Does Scroll Progress hurt performance or accessibility?
No. The fill is a single scaleX transform scrubbed by one GSAP ScrollTrigger tween, so there is no per-frame JavaScript loop of its own and no layout or paint beyond the compositor. The bar is a few pixels tall, sits above content with pointer events disabled so it never blocks reading or interaction, and is marked aria-hidden because it repeats what the scrollbar already conveys; a progressbar role is deliberately not used since per-frame value updates would spam screen readers. With reduced motion enabled the fill is hidden and no scroll-linked animation is created.
How do I track a single article instead of the whole page?
Pass a CSS selector via the target prop, for example target set to #article. The fill then maps its 0 to 100% range to that element, starting when its top reaches the top of the viewport and finishing when its bottom reaches the bottom. Leave target unset to track the whole document.

Related components

Want the full page this belongs to?Template Empire builds complete Next.js templates with this motion system throughout. Browse the paid templates that pair with these free components.Explore Template Empire templates