Loaders✨ Premium

Progress Bar

A 200 × 6 px bar; its 80 px pill (40% of the track), indigo-to-fuchsia gradient, slides from translateX(−100%) to translateX(350%) in 1.5 s ease-in-out, infinite. Zero JavaScript — the whole motion lives in one @keyframes and a ::after selector.

CSSLinear

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Loaders has 29 effects, including 4 free. Explore the category →

3 usage examples

Submit button — form waiting for a server response — Progress Bar example 1

① Submit button — form waiting for a server response

WhenA contact or payment form: on click, the button label disappears and the bar replaces its background during the request. It gives way to the result (success or error) once the response arrives.
WhyThe bar fills the button's exact footprint with no spinning icon added on top. An indeterminate indicator is honest — the server response time is unknown. The left-to-right sweep follows reading direction and implies a move toward confirmation.
Settingswidth: 100% (inherits from the button, typically 140–200 px), height: 4px to stay subtle, border-radius: 0 0 6px 6px if the button has a 6 px radius; pill width: 50%.
Top edge of the window — navigating from one page to the next — Progress Bar example 2

② Top edge of the window — navigating from one page to the next

WhenA site or app with client-side navigation (documentation, admin panel, storefront): the user clicks a menu link and the current page stays on screen until the next one arrives. The bar sits flush against the top edge of the window, full width, outside any container; it disappears once the new page renders.
WhyThis is the original use of this form — the one large sites and front-end routers (Turbo, Nuxt, Next) made familiar. A 4 px bar against the edge reserves no space and covers nothing: a spinner or a skeleton would force you to pick a spot in a page that does not exist yet, or hide the one that does. Stretched to the window's width, it gives the pill a long sweep the eye catches in peripheral vision without leaving what it was reading. And waiting for a page — network, then render — has an unknown duration: the indeterminate indicator is the only honest one.
Settingsposition: fixed; top: 0; left: 0; z-index: 1000 on .loader-bar, width: 100% (the whole window: on 540 px, a 30% pill measures 162 px), height: 4px (instead of the default 6 px), border-radius: 0 on both track and pill — rounded ends make no sense against an edge; duration, easing, track and gradient left at their defaults (1.5 s ease-in-out, rgba(99, 102, 241, .2), indigo → fuchsia).
AI generation — prompt waiting for first streaming tokens — Progress Bar example 3

③ AI generation — prompt waiting for first streaming tokens

WhenAn LLM or autocomplete interface: the user submits a prompt and waits for the first tokens. The bar sits below the input area or above the response zone.
WhyGeneration time is unknown and variable — an indeterminate indicator is the only honest choice. Once streaming starts (first tokens visible), the bar gives way to the incoming text. The slim profile does not compete with the text area.
Settingsanimation-duration: 2s and ease-in-out replaced by linear for steady scrolling with no visible pulsing; gradient linear-gradient(90deg, #6366f1, #818cf8) (cool blue-violet, tech feel); width: 100%.

How it works

The code exposes a single element: .loader-bar, 200 × 6 px, background rgba(99, 102, 241, .2) (indigo at 20% opacity), border-radius: 3px, overflow: hidden. It has no children in the sold HTML; all motion comes from the ::after pseudo-element. That pseudo-element is 40% of the track's width80 × 6 px — and displays the gradient linear-gradient(90deg, #6366f1, #d946ef) (indigo → fuchsia).

barProgress moves the pill from translateX(−100%) to translateX(350%). These values are relative to its own width (80 px): it enters at −80 px (off the track on the left) and exits at 280 px from the track's left edge (off the track on the right; the track is only 200 px). Total travel: 360 px. Duration is 1.5 s, timing function ease-in-out: the pill accelerates as it enters from the left, reaches peak speed at the center and slows before leaving to the right. Measured in Chromium: −67.8 px at 200 ms (still off-track on the left), −0.7 px at 500 ms (pill entering the visible zone). The loop is infinite, delay 0 s: on each restart the pill is instantly back off-track on the left — the jump is hidden by overflow: hidden.

There is no JavaScript in the sold code. Starting or stopping the animation requires only toggling animation-play-state: paused / running on .loader-bar::after, done by adding or removing a class on the track. The 200px width is fixed and not responsive: on a narrower container the bar overflows; on a wider one it stays centered but does not stretch. Replace with width: 100% to follow the parent.

Accessibility

  • prefers-reduced-motion not handled: measured in Playwright with reducedMotion: 'reduce', the pill is at −68.3 px at 200 ms and −2.1 px at 500 ms — the animation runs unchanged. Add @media (prefers-reduced-motion: reduce) { .loader-bar::after { animation: none; } }.
  • No accessibility markup: the bar is an empty div with no role, aria-live, aria-busy or alternative text. For screen readers the loading state is invisible. Add at minimum a sibling element with role="status" aria-live="polite" and visually hidden text: "Loading…".
  • The track is purely decorative: add aria-hidden="true" on .loader-bar and delegate the state to a dedicated element. If the bar is embedded in a button (button in progress), aria-busy="true" goes on the button, not on the bar.
  • Contrast: the pill (#6366f1 → #d946ef) on background #0a0a0f measures 3.4:1 for the indigo end and 3.8:1 for the fuchsia end — above the graphical elements threshold (3:1). On a white background those values drop to 2.8:1 and 3.4:1; lower the lightness or raise saturation to hold the threshold on a light background.

Browser compatibility

Requires @keyframes, translateX(), linear-gradient(), border-radius, overflow: hidden and the ::after pseudo-element with content: "". All of these have been available since Chrome 26, Firefox 16, Safari 7. No dependencies.

Chrome 26+✓ Full
Firefox 16+✓ Full
Safari 7+✓ Full
Edge 12+✓ Full
Mobile iOS 7+✓ Full
Android Chrome✓ Full

Without CSS animation (very old browsers), the track renders empty: the pill exists (the ::after is generated by CSS) but frozen at its initial position (translateX(−100%)), outside the track and invisible. Without linear-gradient (old IE), the pill renders with a transparent background — invisible. Without CSS, the track is an empty div.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="loader-bar"></div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Track width (CSS — .loader-bar, width) 200px Total width of the track. Switch to 100% to follow the parent; the pill's travel adapts automatically because its width is a percentage.
Thickness (CSS — .loader-bar, height) 6px Height shared by the track and the pill (::after height: 100%). 4px for an indicator embedded in a button; 8px for a more visible page header bar.
Track color (CSS — .loader-bar, background) rgba(99, 102, 241, .2) Background of the track. Use the primary hue at 15–25% opacity to stay in the same color family as the pill.
Pill width (CSS — .loader-bar::after, width) 40% (= 80 px on a 200 px track) Fraction of the track the pill covers. 25% = longer sweep, snappier feel; 60% = wide pill that nearly fills the track as it passes.
Pill gradient (CSS — .loader-bar::after, background) linear-gradient(90deg, #6366f1, #d946ef) Color of the pill. A solid color (background: #6366f1) or a reversed gradient (#d946ef, #6366f1) for the opposite visual direction.
Cycle duration (CSS — .loader-bar::after, animation-duration) 1.5s Time for one full pass. 0.8s = urgent feel (network request); 3s = slow process (computation, generation). Also replace ease-in-out with linear for a steady speed beyond 2 s.
Corner radius (CSS — .loader-bar and .loader-bar::after, border-radius) 3px Both must share the same value for consistent track and pill ends. 0 = sharp bar; 9999px = oval pill on a rounded track.

FAQ

Yes. This is the definition of an indeterminate bar: it does not represent a percentage of completion, it signals that an operation is underway without knowing its duration. The opposite behavior — a bar stretching from 0 to 100% — requires knowing the actual progress and animating width via JavaScript. For that, add a CSS variable --progress on .loader-bar::after and update it from your code.
Two common approaches: (1) animation-play-state: paused on .loader-bar::after freezes the pill at its current position — motion stops but the track remains visible. (2) display: none or visibility: hidden on .loader-bar removes it visually. For a polished transition, add opacity: 0; transition: opacity .3s on .loader-bar and toggle the class from JavaScript once the response is received.
The Circular Progress (fx-0432) is a 60 × 60 px SVG whose animation targets stroke-dashoffset — the arc goes from 157 to 0 then to −157 over 2 s (enter then exit). The square format suits a standalone indicator or one placed next to a label. The Progress Bar (fx-0424) is horizontal, 200 × 6 px, and can stretch to 100% of its parent: it belongs above or below content (table, button, text area) and occupies the full available width without adding height.