AI CSS Animations

Guides · · Maxwell Prehoda

14 Progress Bar Animation Examples, From Top Loading Bars to Wavy Material Indicators

A progress bar answers two questions at once: is something happening, and how much is left. The best animated progress bars are honest about the second question. They show a real percentage when one exists, switch to an indeterminate loop when it does not, and never sit frozen at 99%.

Below are 14 progress bars worth copying from: the top-of-page loaders on half the web, YouTube's player bar, Google's Material indicators, Bootstrap's stripes, the native HTML element, scroll-linked reading bars, and two of ours. Each one has what it is best for, the stack it needs, a live demo of the pattern in pure CSS, and the catch.

1. NProgress by Rico Sta. Cruz: Best for slim top-of-page bars in single-page apps

Platform: JavaScript + CSS, MIT, no framework needed

Live from our directory: Top Page Loading Bar. Copy the HTML and CSS →

NProgress is the thin bar along the top of the window that a lot of the web settled on, and its README says plainly that it was "inspired by Google, YouTube, and Medium." Call `NProgress.start()` when a request begins and `NProgress.done()` when it ends. In between, `trickle` nudges the bar forward by small random amounts so it never looks stuck, and `inc()` never reaches 100% on its own.

The defaults are worth stealing even if you never install it: a minimum of 8% so the bar is visible the instant it starts, a 200ms ease for each step, and a fast finish that fills and fades. Our Top Page Loading Bar below is a pure-CSS version of the same pattern, with a glowing head and no script.

Watch out for: The trickle is fake progress by design. That is fine for navigation, but do not use it for uploads where users plan around the number. The repo has seen no commits since 2022.

Live demo ↗

2. Hotwire Turbo's built-in progress bar: Best for Rails and server-rendered apps using Turbo Drive

Platform: Hotwire Turbo, styled with plain CSS

Live from our directory: Indeterminate Progress Bar. Copy the HTML and CSS →

Turbo Drive ships a progress bar you get for free. The handbook says it "appears automatically for any page that takes longer than 500ms to load." It is a single div with the class `turbo-progress-bar`, so you restyle it with ordinary CSS (the handbook's example sets a 5px height and a green background), and `Turbo.setProgressBarDelay` changes the threshold.

That 500ms delay is the real lesson. Fast navigations never flash a bar at all, so the bar only appears when it carries information. Our Indeterminate Progress Bar below is a pure-CSS take on the sliding bar you would put in the same spot.

Watch out for: Its default styles are injected first in the document, so your override must come later in the cascade or it silently loses.

3. nextjs-toploader: Best for route-change bars in Next.js App Router projects

Platform: React component for Next.js, MIT, built on NProgress

Live from our directory: Glowing Progress Bar. Copy the HTML and CSS →

The App Router has no router events to hook NProgress into, which is why this package exists. Its npm description calls it "a Next.js Top Loading Bar component made using nprogress", and it targets Next.js 14 and 15. You drop one component into the root layout and set colour and height as props.

It is the shortest path to the GitHub-style bar in a Next.js app. Our Glowing Progress Bar below is a pure-CSS alternative if you want a more distinctive head than NProgress's default peg: a bright tip that flares when it reaches the end.

Watch out for: A route bar and a loading.js skeleton on the same navigation is two loaders at once. Pick one per route, or keep the bar for routes without a skeleton.

npm registry entry ↗

4. Pace.js: Best for automatic page-load bars with zero wiring

Platform: JavaScript, MIT, about 4kb gzipped plus a theme

Live from our directory: Percentage Progress Bar. Copy the HTML and CSS →

Pace takes a different approach from NProgress: you do not call start or done. It watches Ajax requests (XMLHttpRequest and WebSocket), elements you name, the document ready state and event-loop lag, and estimates progress from all four. It restarts itself on pushState navigation and comes with a set of CSS themes, from a plain bar to corner indicators.

Pick it for a content site or legacy app where you cannot easily hook every request. Our Percentage Progress Bar below is a pure-CSS take on a bar that shows its number, counting from 0 to 100% in a small tooltip that rides the fill.

Watch out for: The estimate comes from heuristics, so it can jump or stall on pages with long-polling or streaming connections. Test it on your slowest real page.

5. YouTube's player and thumbnail bars: Best for video players and resume-watching indicators

Platform: Web and mobile apps

Live from our directory: Video Buffering Progress Bar. Copy the HTML and CSS →

YouTube runs two progress bars most people never think about. On the player, the February 2025 brand refresh turned the played range into a red-to-magenta gradient, angled with magenta on the right, and 9to5Google reported the bar became thinner and quicker in its startup animation. On thumbnails, a red bar added in December 2016 shows how far you got, so partially watched videos read at a glance.

The player bar layers three things: buffered, played and the knob. Our Video Buffering Progress Bar below is a pure-CSS version of that idea, with the buffered range jumping ahead in chunks while the played range follows and stalls when it catches up.

Watch out for: A scrubber is an input, not just an indicator. If users can drag it, it needs slider semantics and keyboard support, which no animation provides.

Tubefilter on thumbnail progress bars (2016) ↗

6. Material Design's indeterminate linear indicator: Best for Android and Material-styled web apps

Platform: Material Components (Android, Compose, web)

Live from our directory: Material Linear Progress Bar. Copy the HTML and CSS →

Google's linear indicator comes in determinate and indeterminate forms. The indeterminate one is the recognisable version: two segments chase across the track, each stretching and shrinking, so the bar feels busy without implying a percentage.

The two-segment chase is what separates it from a simple sliding bar. The segments overlap and change length, so the eye never locks onto one repeating motion. Our Material Linear Progress Bar below is a pure-CSS version of that chase, on Material-style easing curves.

Watch out for: Indeterminate bars are for unknown waits. If you can measure progress, switch to the determinate form as soon as you can, or users will assume the task is stuck.

7. Material 3 Expressive wavy indicator: Best for long processes in playful or expressive apps

Platform: Material Components Android, Jetpack Compose

Live from our directory: Wavy Progress Bar. Copy the HTML and CSS →

Material 3 Expressive added a wavy shape to both linear and circular indicators, with determinate and indeterminate variants and tunable wave parameters. The Android docs say the wavy shape "can make longer processes feel less static" and is best used where a more expressive style fits.

It is the first big design-system progress bar in years that adds character instead of just a colour. Our Wavy Progress Bar below is a pure-CSS version: the filled part is a flowing squiggle, the rest is a flat track ending in a stop dot.

Watch out for: The docs note that wavy linear indicators increase the height of the container, and that at very small sizes the wave can disappear. Reserve the extra height so the layout does not jump.

8. Bootstrap's animated stripes: Best for admin panels and dashboards already on Bootstrap

Platform: Bootstrap 5 CSS, MIT

Live from our directory: Striped Progress Bar. Copy the HTML and CSS →

Add `.progress-bar-striped` and Bootstrap lays a CSS gradient stripe over the bar; add `.progress-bar-animated` and the stripes move right to left with a `1s linear infinite` keyframe. It is the classic install-wizard look, and it is still a clear signal that work is ongoing even when the fill is not moving.

Bootstrap's source also turns the stripe animation off under `prefers-reduced-motion: reduce`. Our Striped Progress Bar below is a pure-CSS version that adds uneven fill steps, so it reads like a real install instead of a smooth fake.

Watch out for: The docs require role="progressbar", an accessible name and aria-valuenow on the wrapper. Without them the stripes are decoration to a screen reader.

9. The native HTML progress element: Best for accessible bars with no library at all

Platform: HTML, every modern browser

`progress` with `max` and `value` is a determinate bar with built-in semantics. Leave out `value` and it becomes indeterminate, which the `:indeterminate` pseudo-class lets you style; call `removeAttribute('value')` to switch back. Styling uses vendor pseudo-elements: `::-webkit-progress-value` in Chrome and Safari, `::-moz-progress-bar` in Firefox.

MDN's accessibility advice is the part most custom bars skip: give it a label, and for a loading region set `aria-busy="true"` on the region and point at the bar with `aria-describedby`, then remove aria-busy when loading finishes.

Watch out for: Each engine styles the native element through different pseudo-elements, and a new value jumps rather than eases by default. If you want a smooth, consistent fill, animate a styled div and keep role="progressbar" and the aria values on it.

10. Scroll-linked reading progress bar: Best for long articles, docs and blog posts

Platform: Pure CSS scroll-driven animations (Chrome 115+)

Chrome for Developers' guide to scroll-driven animations opens with a reading progress indicator: a fixed bar that grows as you scroll, built with `animation: grow-progress auto linear` and `animation-timeline: scroll()`. No scroll listener and no JavaScript, and because it animates transform it can run off the main thread.

The animation is a plain scaleX keyframe from 0 to 1; the timeline does the rest. If you want more scroll-linked patterns, our scroll animation examples and Apple-style scroll guide go further.

Watch out for: Browsers without scroll-driven animations ignore animation-timeline, so the keyframe would run once on load as a normal animation. Wrap it in @supports (animation-timeline: scroll()) to avoid a bar that fills by itself.

11. GOV.UK's advice on form progress: Best for multi-step forms, checkouts and sign-up flows

Platform: GOV.UK Design System guidance

Live from our directory: Stepper Progress Bar. Copy the HTML and CSS →

Not every progress indicator should exist. The GOV.UK Design System tells teams to "start by testing your form without a progress indicator", to only include the total number of questions if you can do so reliably, and notes that "a number of GOV.UK services have removed this style of progress indicator without any negative effects."

If your flow really is a fixed number of steps, a compact stepper beats a long list of every question. Our Stepper Progress Bar below is a pure-CSS stepper: four steps whose connectors fill in turn, each step popping when it becomes current and turning into a check when done.

Watch out for: Branching forms break step counts. If question 4 can lead to 3 or 9 more steps, a "Step 4 of 7" bar will lie to someone.

12. Upload progress in a toast: Best for background uploads users can walk away from

Platform: Pattern used in file and media apps

Live from our directory: Uploading Toast Loader. Copy the HTML and CSS →

Uploads, downloads and conversions are the cases where Nielsen Norman Group recommends a progress bar over a skeleton or spinner, and past about ten seconds it wants an explicit estimate. Putting that bar in a small toast lets the user keep working while it runs.

Our Uploading Toast Loader below is a pure-CSS version of the pattern: a pill with an arrow that keeps launching out of its circle, an "Uploading 3 files" label and a mini bar underneath.

Watch out for: A toast that disappears on its own can hide a failed upload. Keep it until the job finishes or fails, and say which.

13. Our Progress Bars collection: Best for copying a styled bar in plain CSS, no library

Platform: Motions directory, free Ours

Live from our directory: Stacked Progress Bar. Copy the HTML and CSS →

The Progress Bars collection gathers every bar in our loaders directory: indeterminate slides, striped installs, segmented blocks, liquid tubes, storage usage bars and gradient fills. Each is HTML and CSS only, with two custom properties for colour and size and a prefers-reduced-motion guard written in.

The Stacked Progress Bar below is a good example of a bar most collections skip: a storage usage meter where four categories measure in one at a time above a matching legend, with hues rotated from your accent colour.

Watch out for: These are visual. When you wire one to real progress, add role="progressbar" and update aria-valuenow from your code.

14. Our Circular Progress with Percentage: Best for dashboards and onboarding with a single number

Platform: Motions directory, free Ours

Live from our directory: Circular Progress with Percentage. Copy the HTML and CSS →

Sometimes a ring fits better than a bar: a profile that is 60% complete, a quota, a timer. Our Circular Progress with Percentage fills clockwise while the number in the middle counts to 100%, and the counter is pure CSS, driven by a registered custom property rather than JavaScript.

Point the custom property at your real value and the ring and the number move together, so they can never disagree.

Watch out for: Rings are harder to read precisely than bars. Show the number inside, as this one does, if the exact value matters.

How to choose a progress bar animation

Match it to what you know

If you can measure progress (bytes uploaded, steps done), show a determinate bar and a number, like the native progress element or our circular ring. If you cannot, use an indeterminate loop like Material's two-segment chase. If you are guessing, as NProgress's trickle does, keep it to navigation where nobody plans around the figure. Under about a second, show nothing; Turbo's 500ms delay is a good default. For full-page loads where you know the layout, a skeleton is often better: see our skeleton screen examples.

Match it to the place

Route changes get a thin top bar (NProgress, Turbo, nextjs-toploader). Media gets a buffered plus played scrubber like YouTube's. Uploads get a bar inside a toast or the file row. Long reading pages get a scroll-linked bar. Forms get a stepper, and only after you have tested without one, as GOV.UK advises. A button that starts the task can carry its own fill, like our Progress Button Loader.

Match it to your stack

On Rails with Turbo, restyle the bar you already have. On Next.js App Router, use nextjs-toploader. On Bootstrap, add the striped classes. On Android, use the Material indicator or its wavy variant. Anywhere else, copy a pure-CSS bar from the Progress Bars collection, which adds no dependency and already handles reduced motion.

Make your progress bar tell the truth

Find the slowest action in your product and decide which of the three it is: measurable, unmeasurable, or too short to show. That one decision picks the bar. Styling is the easy part.

If you want a bar shaped to your product, describe it in the creator or paste the component it lives in. You get CSS keyframes you can wire to a real value, with reduced motion handled.

A prompt to start from
Make a determinate progress bar for file uploads, 6px tall, rounded, full width of its container.
The fill width comes from a --progress custom property (0 to 1) and eases to new values over 250ms with ease-out.
Add a soft highlight that sweeps through the filled part every 1.8s while uploading.
When --progress reaches 1, flash the fill once and show a check at the right end.
Under prefers-reduced-motion, keep the fill changes but drop the sweep and the flash.
Include role="progressbar" and aria-valuenow in the HTML.

Make your own with AI →

Common questions

How do I animate a progress bar in CSS?

For a determinate bar, set the fill's width or transform: scaleX() from a custom property and add a transition, so each update eases into place. For an indeterminate bar, animate a segment across the track with a looping @keyframes on transform. Animating transform instead of width keeps it on the compositor and smooth.

What is the difference between a determinate and an indeterminate progress bar?

A determinate bar shows how much of a task is done, such as 40%. An indeterminate bar only shows that work is happening, usually with a looping slide. In HTML, a progress element without a value attribute is indeterminate.

How do sites like YouTube and GitHub show a loading bar at the top of the page?

They show a thin fixed bar at the top of the viewport that starts on navigation, creeps forward while the request runs and fills fast when it finishes. Libraries like NProgress, nextjs-toploader and Turbo's built-in bar implement exactly that, and our Top Page Loading Bar does it in CSS.

Can a progress bar show scroll position without JavaScript?

Yes, in browsers that support CSS scroll-driven animations. Give a fixed bar a scaleX keyframe and set animation-timeline: scroll(). Chrome shipped the API in version 115. Wrap it in @supports so other browsers do not play the keyframe on load.

Are animated progress bars accessible?

They can be. Use the native progress element, or give a custom bar role="progressbar", an accessible name, and aria-valuenow, aria-valuemin and aria-valuemax. Stop decorative motion such as stripes or shimmer under prefers-reduced-motion, as Bootstrap does.

Sources