Library / SDK
klendi/react-top-loading-bar avatar
klendi/react-top-loading-bar

react-top-loading-bar: a thin progress bar that follows your route changes

A very simple, highly customisable youtube-like react loader component.

749 stars65 forksTypeScriptMIT

At a glance

What is it?
react-top-loading-bar is a small React component that draws the familiar bar across the top of the browser window while a page loads, and it can be driven by hooks, by a ref, or by your own state. It suits a single page application that needs a visible signal during navigation, and it is a cosmetic indicator rather than a measurement of anything.
Who is it for?
react-top-loading-bar fits a React application that wants a route change indicator without writing the animation, animation timing and container handling by hand, and it does not fit a project that needs a real progress figure from a slow upload, since the bar advances on a timer and on manual calls rather than on measured work.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 21 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A cosmetic bar, not a progress measurement

react-top-loading-bar is described in the package metadata as a very simple, highly customisable react top loader component, and the README frames it as a YouTube-like React loader. The homepage is a live demo at klendi.github.io/react-top-loading-bar, and the repository carries an example directory with a Vite application that is the source of the deployed demo.

The important thing to understand first is what the bar measures. It does not measure anything. There is no underlying request, no upload, no progress event. The bar advances on a timer and on whatever the calling code tells it, and the calling code is responsible for ending it. That makes it an indicator of a state your application knows about, not a report of work completed.

The audience is a React developer who wants the familiar bar across the top of the viewport during a route change or a slow render, and who does not want to hand roll the animation, the fade-out and the fixed positioning. The project is MIT licensed, written in TypeScript, and the last push was on 2026-10-02.

The version history is worth a glance because of what it says about the current state. The most recent tagged releases are v3.0.2 from 2024-12-22 and v3.0.0-beta.1 from the same day, roughly two hours earlier. A 3.0 line that had a beta and a final on one day, with no tag since, describes a major version rewrite followed by a quiet period, which matches a package that has been stable since rather than one that churns.

Three ways to drive it, and what each one costs you

The README documents three integration styles, and the difference between them is the difference between the component owning its state and your application owning it.

The hook style is the shortest. The useLoadingBar hook returns a start and a complete function, and takes options such as color and height directly.

js
import { useLoadingBar } from "react-top-loading-bar";

const App = () => {
  const { start, complete } = useLoadingBar({
    color: "blue",
    height: 2,
  });

  return (
    <div>
      <button onClick={() => start()}>Start</button>
      <button onClick={() => complete()}>Complete</button>
    </div>
  );
};

But the hook style has a catch that the README handles in the very next section, and it is worth not skipping it. The hook renders the bar into a container, so the app has to be wrapped in LoadingBarContainer for the bar to appear at the top of the page.

js
import { LoadingBarContainer } from "react-top-loading-bar";

const Parent = () => {
  return (
    <LoadingBarContainer>
      <App />
    </LoadingBarContainer>
  );
};

The ref style skips the container entirely. The component is rendered directly in your tree with a ref attached, and the methods are called through ref.current.

js
import { useRef } from "react";
import LoadingBar, { LoadingBarRef } from "react-top-loading-bar";

const App = () => {
  // prettier-ignore
  const ref = useRef<LoadingBarRef>(null);

  return (
    <div>
      <LoadingBar color="#f11946" ref={ref} shadow={true} />
      <button onClick={() => ref.current?.continuousStart()}>
        Start Continuous Loading Bar
      </button>
      <button onClick={() => ref.current?.staticStart()}>
        Start Static Loading Bar
      </button>
      <button onClick={() => ref.current?.complete()}>Complete</button>
    </div>
  );
};

That is the style to pick if you dislike the container wrapper, and it is also the style that gives you direct access to the finer grained methods, since continuousStart and staticStart are separate calls here.

The state style is the third option, and it is the one that hands the most control to the application. You keep the progress number yourself and pass it in.

js
import LoadingBar from "react-top-loading-bar";

const App = () => {
  const [progress, setProgress] = useState(0);

  return (
    <div>
      <LoadingBar
        color="#f11946"
        progress={progress}
        onLoaderFinished={() => setProgress(0)}
      />
      <button onClick={() => setProgress(progress + 10)}>Add 10%</button>
      <button onClick={() => setProgress(progress + 20)}>Add 20%</button>
      <button onClick={() => setProgress(100)}>Complete</button>
    </div>
  );
};

The onLoaderFinished callback matters here. When the bar reaches the end it reports back, and the example resets the state to zero, so the next run starts from the beginning rather than from a bar that is already full. Without that reset, a second navigation would show a bar that jumps straight to the end.

The two start modes, and why the bar stops at 90 percent

The built-in methods table is the most informative part of the documentation, because the numbers in it are what make the animation feel the way it does.

The start method takes an optional loader type, continuous by default or static. The continuous mode starts the animated bar and keeps it moving, and the static mode starts a bar that grows once and then sits. There is a separate staticStart method with its own behaviour, and the table describes it as starting the indicator with a random starting value between 30 and 50.

The continuous method is where the design becomes visible. continuousStart takes an optional starting value and an optional refresh rate, and the description says it starts the indicator with a random starting value between 20 and 30, then repetitively after a refresh rate increases it by a random value between 2 and 10, and that this continues until it reaches 90 percent of the indicator's width.

Two things follow from that description. The bar never reaches the end on its own, because 90 percent is the ceiling, so the last 10 percent is what visually means the work is finishing. And the increments are random, which is the detail that makes the movement look like the YouTube bar rather than a linear progress bar. A reader who expects a percentage to increase steadily will find the behaviour different, and that is deliberate.

The complete method makes the indicator reach 100 percent of its width and then fade. Alongside it, increase and decrease add and subtract a value, and getProgress returns the current value, which is useful when the application keeps its own copy of the number.

Properties are plain CSS, with sensible defaults already chosen

The properties table shows a component that is styled almost entirely through props, with defaults that already look right.

The progress prop is a number that varies from 0 to 100 and defaults to 0. The color prop takes the same values as the CSS background property, so red, #000 and rgb(255,0,0) all work, and it defaults to red. The shadow prop is a boolean that defaults to true, which enables the shadow underneath the loader. The height prop is a number in pixels and defaults to 2, a thin bar. The background prop sets the parent container background and defaults to transparent, so the bar does not paint a white strip across a dark page. Style and containerStyle accept CSSProperties for the loader's div and for its container.

Two of these defaults are opinionated in a way that matters. A two pixel bar with a shadow, and a transparent container, is the YouTube look rather than a generic progress bar, and the transparency default is what lets the component sit over a dark themed application without a visible band. Anyone coming from a design system that wants a four pixel bar will need to set height explicitly.

The shadow default is worth a second look. A shadow on a two pixel element is subtle but not invisible, and it is a rendering cost on every navigation in an application that navigates often. It is a boolean, so turning it off is one prop.

Packaging, peer range, and what a consumer actually downloads

The package metadata describes a small, conventional npm package, and the details narrow down who can use it.

The published entry points are an ES module at ./dist/index.mjs, a CommonJS build at ./dist/index.js, and type declarations at ./dist/index.d.ts, exposed through an exports map with require, import and types conditions. The source is a single file, src/index.tsx, and the build runs through tsup. The files field publishes only the dist directory, so the source and the example application are not part of what a consumer installs.

The peer dependency is react at a range of ^16 || ^17 || ^18 || ^19, which is unusually generous and covers everything from React 16 through React 19. The engines field asks for Node 14 or newer. The development dependencies are the current React 19, TypeScript 5.7, tsup, prettier and gh-pages, and the deploy script publishes the built example directory to GitHub Pages with gh-pages, which is how the hosted demo is produced.

The scripts section also contains a test:build entry that is simply npm run build, which is worth noting because it means the naming suggests a test suite that the script does not actually perform. The repository does have a .github directory, and the presence of a CI badge in the README suggests some automated check runs, but the documentation does not describe what is checked.

The package is MIT licensed, and the metadata names a single author, Klendi Goci, with the GitHub profile linked. The type declarations are shipped, so a TypeScript consumer gets the LoadingBarRef type used in the ref example without a separate types package.

Where this sits against writing the bar yourself

The realistic alternative is not another library, it is a hundred lines of your own CSS and a timeout, so the comparison is worth making directly.

A hand written version of this idea is a fixed position div at the top of the viewport with a width that transitions over a second or two, and a call to start and complete. The difficult parts are all in the details, and they are the parts this package already handles. The bar has to sit above the page content without creating a scrollbar, so it is positioned fixed and the container has to be pointer-transparent, otherwise it swallows clicks on the content underneath. It has to be above a sticky header without being clipped by an overflow hidden ancestor. It has to fade out rather than snap away, and it has to reset so a second load does not flash a full bar.

That is the argument for using it. The counter argument is about the abstraction itself. The bar advances on a timer, not on a real signal, so a slow application that calls start and then completes after eight seconds will show a bar that sat near 90 percent and then jumped, which is arguably more misleading than no bar at all. And an application with a genuine progress figure, an upload with a content length or a batch job with known steps, is better served by a component that takes the real number, which the state style of this package does support but which then means you have implemented the rest yourself.

So the fit is narrow and clear. Use it for route changes and unknown duration waits, where a moving bar is honest about the uncertainty. For known duration work, drive it from state with real numbers.

Editorial conclusion

react-top-loading-bar fits a React application that wants a route change indicator without writing the animation, animation timing and container handling by hand, and it does not fit a project that needs a real progress figure from a slow upload, since the bar advances on a timer and on manual calls rather than on measured work. Before adopting it, read the methods table and decide whether the automatic behaviour suits your routing library, because the component advances on its own refresh rate until it reaches 90 percent and you are responsible for calling complete, and check that your React version falls in the supported peer range of 16 through 19.

Frequently asked questions

How do I install react-top-loading-bar?

With npm using npm install --save react-top-loading-bar, with yarn using yarn add react-top-loading-bar, or from a CDN at https://unpkg.com/react-top-loading-bar. The package requires React 16, 17, 18 or 19 as a peer dependency and Node 14 or newer.

What is the difference between continuousStart and staticStart in react-top-loading-bar?

continuousStart starts the animated bar with a random starting value between 20 and 30 and then increases it by a random value between 2 and 10 on each refresh, continuing until it reaches 90 percent of the width. staticStart starts the indicator with a random value between 30 and 50 and does not keep animating.

Do I need to wrap my app in LoadingBarContainer?

If you use the useLoadingBar hook, yes, the README shows the app wrapped in LoadingBarContainer so the bar renders into the container. If you prefer a ref, you can render the LoadingBar component directly in your tree with a ref and call the methods through ref.current, and no container wrapper is needed.

What happens when the loading bar reaches the end in react-top-loading-bar?

The onLoaderFinished callback fires, and the example in the README uses it to reset the progress state to zero so the next run starts from the beginning. The complete method makes the indicator reach 100 percent of its width and then fade.

Can I style the react-top-loading-bar component?

Yes. The color prop takes CSS background values, height sets the bar thickness in pixels, shadow toggles the shadow underneath the loader, background sets the container background, and style and containerStyle accept CSSProperties for the loader div and its container.

Official sources

  1. klendi/react-top-loading-bar on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/klendi-react-top-loading-bar.svg)](https://hysenlabs.com/projects/klendi-react-top-loading-bar)

Was this useful?