Scroll Progress Bar — Free HTML CSS JS Snippet

Scroll Progress Bar · Scroll · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

scrollTop / (scrollHeight - clientHeight) formula — correct percentage from 0 to 100
position: fixed; top: 0; left: 0 — always at the top of the viewport
linear-gradient(90deg) gradient fills as bar width grows
transition: width 0.1s linear — smooth animation between scroll events
z-index: 999 — sits above page content
height: 3px — subtle, non-intrusive reading indicator
4 lines of vanilla JavaScript — no library needed
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons
Live split-pane editor — preview updates as you type

About this UI Snippet

Scroll Progress Bar — scrollTop Formula, Gradient Bar & Fixed Position

Screenshot of the Scroll Progress Bar snippet rendered live

A scroll progress bar is a thin bar at the top of the page that fills from left to right as the user scrolls down — often combined with a sticky header and a scroll-to-top button. It communicates reading progress on long-form content — blog posts, documentation, articles, and terms pages. Users can see at a glance how far through the content they are and how much remains.

The scroll percentage formula

The JavaScript listener is two lines: document.addEventListener('scroll', () => { const pct = doc.scrollTop / (doc.scrollHeight - doc.clientHeight) * 100; bar.style.width = pct + '%'; }).

document.documentElement.scrollTop is the number of pixels scrolled from the top. document.documentElement.scrollHeight is the total height of the document. document.documentElement.clientHeight is the visible viewport height. Subtracting clientHeight from scrollHeight gives the maximum scrollable distance — the amount you can actually scroll before hitting the bottom. Dividing scrollTop by this and multiplying by 100 gives a percentage from 0 to 100 that maps directly to scroll position.

The CSS gradient bar

The bar uses background: linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899) — an indigo-violet-pink gradient. As the width grows, the gradient fills proportionally from left to right. The bar is height: 3px; position: fixed; top: 0; left: 0; z-index: 999 — always pinned to the top of the viewport above all other content.

The transition smoothing

transition: width 0.1s linear smooths the width change between scroll events. Without it, the bar would jump discretely on each scroll event. The 0.1s linear duration is short enough that the bar feels responsive but long enough to hide any jitter between scroll events.

Using on a specific element

The current implementation tracks scroll on document.documentElement. To track scroll progress within a specific scrollable container (a div with overflow-y: auto), replace doc.scrollTop with container.scrollTop and doc.scrollHeight - doc.clientHeight with container.scrollHeight - container.clientHeight. Add the scroll listener to the container element instead of document.

Changing the gradient

Update the three colour values in linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899) to any colours. Use two colours for a simpler gradient or a single solid colour by replacing the gradient with a plain background value.

The scrollTop / scrollHeight formula

The scroll progress is computed as: const pct = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100. window.scrollY is the number of pixels scrolled from the top. scrollHeight - innerHeight is the maximum scrollable distance — the total page height minus the viewport height. This gives a 0–100% value that fills the bar from empty to full as the user scrolls from top to bottom.

Fixed positioning

The progress bar uses position: fixed; top: 0; left: 0; right: 0; height: 4px; z-index: 9999. Fixed positioning keeps the bar at the viewport top regardless of scroll position. A high z-index ensures it sits above the page navigation. The bar uses no border-radius at the right end (border-radius: 0 2px 2px 0) so it appears to extend from the left edge of the screen.

The gradient fill

The fill element uses background: linear-gradient(90deg, var(--accent), var(--accent-secondary)) for a two-tone gradient that sweeps from left to right as the user reads. The gradient adds visual interest compared to a flat colour while communicating direction (left = start, right = progress toward completion).

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't need to re-derive the scrollTop formula from scratch every time. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why scrollHeight minus clientHeight (not scrollHeight alone) is the correct denominator, or why a raw scroll listener with no throttling is safe here when the handler only does one division and one style write. The same assistant can help optimize it — asking whether requestAnimationFrame throttling would matter if the bar's logic grew more expensive, or whether a ref-based update (versus React state) is the right call for avoiding re-renders on every scroll tick. It's also useful for extending the effect: ask it to scope the same formula to a specific scrollable container instead of the whole document, animate the gradient's hue as a function of scroll percentage, or add an aria-valuenow-driven progressbar role for accessibility. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "scroll progress bar" in plain HTML, CSS, and JavaScript using only the native scroll event — no library, no requestAnimationFrame throttling needed given the handler's simplicity.

Requirements:
- A thin bar element fixed to the very top of the viewport, spanning the full width, starting at 0% width, with a left-to-right multi-color linear gradient background and a high z-index so it sits above all page content.
- A single scroll event listener on the document that, on every event, computes the scroll percentage as document.documentElement.scrollTop divided by (document.documentElement.scrollHeight minus document.documentElement.clientHeight), multiplied by 100.
- Apply that computed percentage directly to the bar's width as a percentage string on every scroll event, with a short CSS transition (e.g. width 0.1s linear) so the width change smooths out between scroll events instead of jumping in visible steps.
- Explain in a comment or accompanying note why subtracting clientHeight from scrollHeight is required — dividing scrollTop by scrollHeight alone would mean the bar never reaches 100% because scrollTop's maximum value is always scrollHeight minus clientHeight, never scrollHeight itself.
- Make the formula reusable against any specific scrollable container element instead of the whole document by substituting the container's own scrollTop, scrollHeight, and clientHeight, and attaching the listener to that container rather than to document.

Step by step

How to Use

  1. 1
    Scroll in the previewScroll down in the preview panel to see the gradient bar fill from left to right. The bar tracks scroll position in real time.
  2. 2
    Change the gradient coloursIn the CSS panel, update the three hex values in linear-gradient(90deg, ...) to your brand colours.
  3. 3
    Change the bar heightUpdate height: 3px on .progress-bar in the CSS panel. 2px is subtle, 4px is more prominent.
  4. 4
    Move to a specific scrollable containerIn the JS panel, replace doc.scrollTop and doc.scrollHeight references with your container element and add the scroll listener to that element.
  5. 5
    Add to a fixed navigation barChange the bar position from top: 0 to be flush with the bottom of your fixed nav. Set z-index higher than the nav if it overlaps.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Blog posts and articles
Show reading progress on long-form content so readers can see how far through the post they are and how much remains.
Documentation and guides
Add to developer documentation or long tutorial pages where users need to track their place through multi-section content.
Learn the scroll percentage formula
Edit the JavaScript formula in the JS panel. Change which element is tracked and how the percentage is calculated to understand the scrollTop / scrollHeight math.
Brand-matched reading indicator
Update the gradient colours to your brand palette for a branded reading progress bar. Or use a solid colour for a more minimal look.
Terms and privacy policy pages
Show users how far through a legal document they have read. Combine with a checkbox that enables only after the bar reaches 100%.
Scroll-tracked dashboard sections
Track scroll position within a specific overflowing container — a dashboard feed, a chat log, or a data table — by swapping the document references for the container element.

Got questions?

Frequently Asked Questions

scrollTop is the number of pixels scrolled from the top. scrollHeight is the total document height. clientHeight is the visible viewport height. scrollHeight - clientHeight is the maximum scrollable distance (you cannot scroll past this). Dividing scrollTop by this value gives a 0–1 ratio; multiply by 100 for the percentage.

If you divided scrollTop by scrollHeight directly, the bar would never reach 100% — because even when scrolled to the bottom, scrollTop is less than scrollHeight by exactly clientHeight. Subtracting clientHeight gives the true maximum scroll position.

Replace document.documentElement with your container element: const container = document.getElementById("my-container"). Replace doc.scrollTop with container.scrollTop and (doc.scrollHeight - doc.clientHeight) with (container.scrollHeight - container.clientHeight). Add the listener to container instead of document.

Update the colour values in linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899). Use two colours for a simpler gradient, or replace the entire gradient with a solid background colour for a minimal bar.

Not for this use case. The scroll handler does only a division and a style assignment — both are extremely fast. Debouncing would make the bar lag noticeably behind scrolling. The transition: width 0.1s handles smoothness without debounce.

Yes. Click "JSX" to download a React component. In React, add the scroll listener in a useEffect on mount and clean it up on unmount. Update a percentage state variable on each scroll event and apply it as a style prop: style={{ width: pct + "%" }} on the bar div.