Top Loading Bar — Route Progress HTML CSS JS Snippet

Top Loading Bar · Loaders · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Trickle-to-90% model
startBar eases the target up by a shrinking step every 400ms, capped at 90%, faking confident progress for an unknown duration.
Complete and fade
finishBar sets the target to 100%; once eased past 99.5% the bar fades via opacity and resets for the next run.
rAF width tween
A requestAnimationFrame loop eases displayed width toward the target each frame, so the bar glides between trickle steps.
Export-safe animation
Width is set in JS (not a CSS width transition), so the motion is identical in the React + Tailwind export; only the fade uses opacity.
Glowing peg
A ::after with layered box-shadows trails the leading edge, the signature NProgress light effect.
Fixed, top-layer bar
position: fixed; top: 0 with a high z-index keeps the bar above all content during loads.
Manual + auto controls
simulate runs a full start-to-finish cycle; startBar/finishBar let you drive the phases independently.
Re-entrant safe
Guards prevent overlapping starts and the loop resets state on completion, so rapid navigations behave predictably.

About this UI Snippet

Top Loading Bar — Trickle-to-90%, Complete-and-Fade & Glowing Peg

Screenshot of the Top Loading Bar snippet rendered live

The thin progress bar that slides across the top of the page during navigation — popularised by YouTube, GitHub, and the NProgress library — has become the standard signal that "something is loading". It works precisely because the duration of a page load or fetch is unknown: instead of a fake percentage, it trickles quickly toward 90%, holds, and then snaps to 100% and fades once the real work finishes. This snippet implements that behaviour in plain HTML, CSS, and vanilla JavaScript.

The trickle model

A real loader can't know how long a request will take, so this bar fakes confident progress. startBar sets the target to 8% and starts a setInterval "trickle" that nudges the target upward by a shrinking amount every 400ms but never past 90% (target + (0.9 − target) × 0.2). This easing means it races early and slows as it approaches 90%, conveying activity without ever pretending to be done. When the actual work completes, finishBar clears the trickle and sets the target to 100%.

Smooth JS-driven width

A requestAnimationFrame loop eases the *displayed* width toward the target each frame (shown += (target − shown) × 0.14), so the bar glides rather than jumping between trickle steps. The width is set directly in JS rather than via a CSS width transition — deliberately, because utility frameworks like Tailwind don't animate raw width, so a JS tween guarantees identical smoothness across the React and Tailwind exports. Only the final fade uses an opacity transition (which frameworks do animate).

Complete and fade

When finishing and the bar has eased past 99.5%, the loop fades it out via opacity, then resets width to zero after the fade so the next navigation starts clean. The bar is position: fixed at the very top with a high z-index, and a glowing "peg" (::after with layered box-shadows) trails the leading edge — the signature NProgress detail that makes the bar feel like light moving across the screen.

Demo controls

A mock browser window with "Navigate (auto)", "Start", and "Finish" buttons lets you trigger the full cycle or drive the phases manually. simulate runs a realistic start-then-finish after 2.2 seconds.

In a real app you call startBar() when a route change or fetch begins and finishBar() when it resolves (router events, fetch interceptors, or a global request counter). Pair this with a skeleton loader for content placeholders, a progress bar for determinate tasks, or a download button for in-button progress.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI coding assistant like Claude to trace through the trickle formula in startBar() — target + (0.9 - target) * 0.2 + 0.01 — with a few sample target values to see exactly why it races early and slows to a crawl approaching 90%, and why that specific shape is what makes an unknown-duration load feel confident rather than fake. It's worth asking about the architecture choice too: why width is tweened in JS via requestAnimationFrame instead of a CSS width transition, and what would actually break in the Tailwind export if it weren't. For extending it, ask for a version that reports real byte progress when it's available (falling back to the trickle only when it isn't), a color that shifts as the bar approaches completion, or multiple independent bars for concurrent requests that merge into one visible bar. 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 YouTube/GitHub-style top-of-page loading bar in plain HTML, CSS, and JavaScript that fakes confident progress for a request of unknown duration — no library, no CSS width transition.

Requirements:
- A thin, fixed-position bar pinned to the very top of the viewport with a high z-index, whose width is set directly via JavaScript on every frame, not via a CSS transition on the width property.
- A start function that immediately jumps the bar's target to a small initial value (such as 8%), then begins a repeating interval that nudges the target upward by a shrinking amount each tick — using a formula based on the remaining distance to 90% — so the target approaches but never reaches 90% on its own.
- A requestAnimationFrame loop that, every frame, eases the actually-displayed width a fraction of the way toward the current target (not snapping straight to it), so the bar visually glides between each trickle step rather than jumping.
- A finish function that stops the trickle interval and sets the target to 100%, letting the same easing loop carry the displayed width the rest of the way.
- Once the eased width crosses very close to 100% (such as above 99.5%), the loop must switch the bar to a CSS opacity fade-out, and only after that fade completes reset the width back to zero so the next run starts clean.
- Add a glowing highlight at the bar's leading edge using layered box-shadows on a pseudo-element, and provide simple manual "Start" and "Finish" trigger buttons plus a combined "simulate a full navigation" button for testing the whole cycle.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA mock browser window with controls appears, and a thin gradient bar sits ready at the very top of the page.
  2. 2
    Click "Navigate (auto)"The top bar slides in, trickles quickly toward 90% and holds, then after ~2.2s jumps to 100% and fades — a full load cycle.
  3. 3
    Watch the trickleNotice it races early and slows near 90%, never completing until the real work finishes — confident progress for an unknown duration.
  4. 4
    Drive it manuallyClick "Start" to begin trickling and "Finish" to complete and fade, mimicking a route change resolving.
  5. 5
    See the glowing pegA soft glow trails the bar's leading edge as it moves — the signature NProgress light effect.
  6. 6
    Wire it to your appCall startBar() on route-change/fetch start and finishBar() on resolve; the bar handles the rest.

Real-world uses

Common Use Cases

SPA route transitions
Show progress on client-side navigation in React/Vue/Angular routers. Pair with a skeleton loader for the incoming page.
Page and form submissions
Trigger on form submit or full-page navigation so users know the request is in flight.
AJAX / fetch activity
Start on the first in-flight request and finish when the global request count hits zero — an app-wide loading indicator.
Dashboard data refreshes
Signal background data reloads at the top of the page without blocking the UI; complements a realtime line chart.
File and asset loading
Indicate heavy asset or report generation; for in-control progress use a download button.
Multi-step flows and wizards
Show a top-level "working" cue while a step processes, alongside a progress bar for the determinate part.

Got questions?

Frequently Asked Questions

Call startBar() when navigation/fetch begins and finishBar() when it resolves. For routers, use their start/complete events (e.g. Next.js routeChangeStart/routeChangeComplete, Vue Router beforeEach/afterEach). For fetch, wrap your client to increment a counter on request and decrement on response, calling startBar when the counter goes 0→1 and finishBar when it returns to 0.

Most navigations and fetches don't expose a reliable progress value, so a real percentage isn't available. Trickling toward 90% communicates "actively working" without lying about completion, then snapping to 100% on the real finish gives a satisfying, honest end. For downloads that DO report bytes, use a determinate bar like the download button instead.

The width changes in unpredictable steps (each trickle tick), so a fixed-duration CSS transition would stutter. A requestAnimationFrame ease toward the moving target stays smooth. It's also export-safe: Tailwind's transition utility doesn't animate raw width, so a CSS-transition bar would snap in the React + Tailwind build — the JS tween behaves identically everywhere.

Treat them as a finish: call finishBar() in your router's error/abort handler and your fetch's catch/finally so the bar always completes and fades rather than hanging at 90%. If you maintain a request counter, ensure failed requests still decrement it so the count can reach zero.

Keep the bar as a single fixed element and drive it from a small module exposing start/finish (the functions here). In React, call them from a router effect; in Vue from router guards in onMounted; in Angular from Router events in a root component. The trickle and rAF logic are framework-agnostic and port unchanged — or wrap them in a tiny store/service.