22 CSS Progress Bars

A CSS progress bar communicates a value — bytes uploaded, form step, storage used, password strength, tokens streamed. These 22 hand-coded patterns cover the native <progress> element, role="progressbar" ARIA fallback, XHR upload progress, ReadableStream streaming, view-transition page loads, multi-step trackers, dark-mode color-mix() tokens, WCAG 2.2 semantics, and video buffered-range overlays. Every demo is scoped under a unique .pb-NN root, guards motion behind prefers-reduced-motion, and drops into React, Vue, Svelte, or Astro without framework lock-in.

8 pure CSS14 light JSPublished 10 new designs · Updated on

Related12 CSS Steppers20 CSS Loaders22 CSS Skeleton Loaders

Native HTML Progress Element with ARIA Fallback — preview
01 / 22Pure CSSNEW

Native HTML Progress Element with ARIA Fallback

The decision primer: a real &lt;progress value="68" max="100"&gt; on the left, a custom role="progressbar" div on the right, both at the same value so you can compare what each one buys you. Includes the complete ::-webkit-progress-bar / ::-webkit-progress-value / ::-moz-progress-bar styling block that makes the native element look designed in every engine.

Published

Indeterminate Progress Bar with aria-busy State — preview
02 / 22Light JSNEW

Indeterminate Progress Bar with aria-busy State

The bar for work whose length you cannot know yet: a sliding fill sweeping a track on a 1.8s linear loop while aria-busy="true" sits on the container and a polite live region announces the state once. Hit Complete and it snaps into a determinate 100% bar — the exact transition production loaders get wrong.

Published

Fetch Upload Progress Bar with XHR onprogress Event — preview
03 / 22Light JSNEW

Fetch Upload Progress Bar with XHR onprogress Event

The upload card every file picker needs: filename, byte counter, live percentage, cancel button, and a completion state with a real checkmark. The bar is driven by xhr.upload.onprogress — the only browser event that reports upload bytes, since fetch() still cannot.

Published

Streaming Response Progress with ReadableStream Reader — preview
04 / 22Light JSNEW

Streaming Response Progress with ReadableStream Reader

Progress for a response that arrives in pieces: a bar advancing as chunks land from response.body.getReader(), a live token counter, a chunk counter, and a pulsing status dot that becomes a checkmark when the stream closes. The AI-SDK pattern, built from the actual reader loop.

Published

View Transition Page Load Bar (NProgress Style Route Change) — preview
05 / 22Light JSNEW

View Transition Page Load Bar (NProgress Style Route Change)

The 3px bar at the top of the window that every SPA needs: it rushes to 80% in 200ms, holds while the route resolves, then completes and fades. Wired to document.startViewTransition() so the bar and the page swap belong to the same navigation.

Published

Password Strength Meter with Live Feedback — preview
06 / 22Light JSNEW

Password Strength Meter with Live Feedback

A four-segment strength meter that grades as you type, with a live requirement checklist beside it and a status line linked to the input through aria-describedby. Colour carries the grade, but so does the word — never colour alone.

Published

Download Queue Progress with Per File Bars and Overall Total — preview
07 / 22Light JSNEW

Download Queue Progress with Per File Bars and Overall Total

Four parallel downloads with their own bars, speeds and pause buttons, under one aggregate header that reports the weighted total — not the average of four percentages, which is the bug almost every queue UI ships with.

Published

Dark Mode Progress Bar with color-mix Tokens — preview
08 / 22Light JSNEW

Dark Mode Progress Bar with color-mix Tokens

One bar, two themes, no duplicated rules: the track is color-mix(in oklab, currentColor 15%, transparent) and the fill is the accent mixed with a touch of white, so both re-derive themselves from whatever colour the surface inherits. Light and dark cards render side by side to prove it.

Published

WCAG 2.2 Accessible Progress Bar with role progressbar Semantics — preview
09 / 22Light JSNEW

WCAG 2.2 Accessible Progress Bar with role progressbar Semantics

The reference implementation, annotated: every required and recommended ARIA attribute labelled in a side panel, a live region that echoes what a screen reader would say, and a focus indicator built to WCAG 2.2's 2.4.13 focus-appearance rules. Copy this one when an audit is coming.

Published

Video Buffering Progress with Buffered Range Overlay — preview
10 / 22Light JSNEW

Video Buffering Progress with Buffered Range Overlay

The player bar with two truths in one track: a light buffered range from video.buffered and a bright played fill from currentTime, with a scrubber handle riding the playhead. Buffered runs ahead of playback, exactly as a real network does.

Published

Liquid Fill Progress Bar for Storage and Quota Meters — preview
11 / 22Pure CSS

Liquid Fill Progress Bar for Storage and Quota Meters

A quota meter that fills like a vessel: two SVG wave layers drifting at different speeds over a rising liquid level, with the percentage set in the middle of the glass. Entirely CSS — the level is chosen by radio inputs, so there is no JavaScript at all.

PublishedUpdated

Multi Step Form Completion Progress Tracker — preview
12 / 22Light JS

Multi Step Form Completion Progress Tracker

A five-step checkout tracker: numbered circles joined by a fill line that grows as you advance, completed steps checkmarked, the current step ringed, upcoming steps muted. Every advance rewrites aria-valuenow so the progression is announced, not just drawn.

PublishedUpdated

Conic Gradient Ring Progress Indicator — preview
13 / 22Pure CSS

Conic Gradient Ring Progress Indicator

Circular progress with no SVG at all: one conic-gradient whose sweep angle is calc(var(--value) * 3.6deg), made animatable by registering --value with @property. Three sizes from the same six declarations.

PublishedUpdated

Segmented Battery Style Progress Bar — preview
14 / 22Pure CSS

Segmented Battery Style Progress Bar

A ten-cell battery meter that changes character with its charge: accent above 30%, amber from 10 to 30, red below, with a lightning bolt and a cell-by-cell charging animation. Colour never travels alone — every state also writes its own label.

PublishedUpdated

Animated Stripe Loading Bar — preview
15 / 22Pure CSS

Animated Stripe Loading Bar

The striped bar, rebuilt properly: 45° repeating-linear-gradient hatching that slides forever, shown as both a determinate fill with a value label and an indeterminate full-track sweep — so you can see exactly which one your state needs.

PublishedUpdated

Gradient Pulse Progress Bar — preview
16 / 22Pure CSS

Gradient Pulse Progress Bar

The onboarding bar that looks alive: a violet-to-cyan fill with a highlight sheen travelling across it and a glow that breathes on a 3s cycle. Two ambient animations, both compositor-friendly, both switched off under reduced motion.

PublishedUpdated

Fundraising Goal Progress Tracker with Milestones — preview
17 / 22Pure CSS

Fundraising Goal Progress Tracker with Milestones

A campaign bar that means something: milestone ticks at 25/50/75/100 with money labels, a callout pinned above the current amount, and a days-remaining pill. The pointer is positioned from the same --value as the fill, so they can never drift apart.

PublishedUpdated

Skill Level Progress Bars for Portfolio Sites — preview
18 / 22Light JS

Skill Level Progress Bars for Portfolio Sites

Portfolio skill bars that fill as they scroll into view, driven by animation-timeline: view() where it exists and an IntersectionObserver where it does not — with the level written as a word next to every bar, because "87%" of a skill is a number nobody can verify.

PublishedUpdated

Circular Counter Progress Ring with SVG Stroke Dashoffset — preview
19 / 22Light JS

Circular Counter Progress Ring with SVG Stroke Dashoffset

The hero ring: an SVG circle with pathLength="100" so the dash maths is just the percentage, a number that counts up on requestAnimationFrame, and a soft accent glow. No circumference calculation anywhere — change the radius and nothing breaks.

PublishedUpdated

Stacked Category Progress Bar for Budget and Analytics — preview
20 / 22Pure CSS

Stacked Category Progress Bar for Budget and Analytics

One bar, five categories, no chart library: segments sized by flex-grow from their own percentage, a legend with values, and a CSS-only tooltip on hover and keyboard focus. The pattern behind every budget and storage-breakdown panel.

PublishedUpdated

Speed Test Gauge Progress with Needle and Ticks — preview
21 / 22Light JS

Speed Test Gauge Progress with Needle and Ticks

A 240° speedometer with a logarithmic scale (10 to 1000 Mbps), tick labels on the arc, and a needle that sweeps to the measured value. Log scale because the difference between 10 and 50 matters far more to a user than the difference between 900 and 950.

PublishedUpdated

Media Buffer Bar with Loaded and Playhead Positions — preview
22 / 22Light JS

Media Buffer Bar with Loaded and Playhead Positions

The podcast scrubber: three stacked layers — muted track, loaded range, playhead fill — with a dot at the playhead and elapsed/remaining labels flanking the bar. Slim by default, thicker on hover and focus, and keyboard-seekable.

PublishedUpdated

Build your own

Tweak the exact look in our visual generators — no signup, instant copy-paste.

FAQ

Frequently asked questions

When should I use the native HTML progress element vs a div with role progressbar?
Prefer the native progress element when the bar visualises a value from 0 to max — assistive technology announces the value change automatically, keyboard focus lands correctly, and no ARIA is needed. Use a div with role=progressbar plus aria-valuenow, aria-valuemin, aria-valuemax when you need custom styling that the browser progress bar cannot render (segmented, ring, gauge, buffered range, stacked category). The native element can be styled via ::-webkit-progress-bar and ::-webkit-progress-value on Chromium and Safari, plus ::-moz-progress-bar on Firefox, but multi-layer visuals like the video buffered range are painful with the native element and cleaner with a custom role=progressbar div. For indeterminate state, either drop the value attribute from progress or set aria-busy=true on the container. Real product surfaces: GitHub file uploads use custom bars for the per-file layout with an overall aggregate, Notion database progress uses native progress inside table cells, Linear roadmap milestones use custom conic rings, Vercel deploys use a top-of-viewport thin line similar to NProgress driven by view transitions on route change.
How do I wire a real file upload progress bar with fetch or XHR?
The native fetch API has no upload-progress event yet, so upload progress bars still use XMLHttpRequest in production. The pattern: create an XHR, attach an xhr.upload.onprogress handler that reads event.loaded / event.total, and paint the ratio to --value on your bar container. Update aria-valuenow and aria-valuetext (aria-valuetext="1.2 megabytes of 2.4 megabytes uploaded") so screen readers announce human-friendly progress instead of raw percentages. For download progress from fetch, use response.body.getReader() and read chunks with Content-Length from response headers as the denominator. Cloudinary, Uploadthing, S3 presigned uploads, and Supabase storage all expose progress callbacks that resolve to the same numbers — plug them into the same CSS custom property. AbortController lets users cancel: xhr.abort() or controller.abort() respectively, then reset the bar and flip aria-busy off. For chunked uploads (files >100 MB), track per-chunk completion and aggregate — the download-queue pattern in demo 07 shows the layout.
How do I show streaming response progress from ReadableStream (SSE, OpenAI, Anthropic, Vercel AI SDK)?
Server-Sent Events and streaming completions from OpenAI / Anthropic / Vercel AI SDK / LangChain arrive as chunks with no upfront total, so the progress bar becomes indeterminate — an animated stripe or sliding fill with aria-busy=true and no aria-valuenow. When the stream ends, flip aria-busy=false and snap the bar to full. If your backend sends a Content-Length header on the streaming response you can compute a determinate value from bytesReceived / total, but most LLM streams omit it because the total isn't known until the last token. A common alternative is a token counter — read event.data for each SSE message, parse the token payload, and increment a visible counter next to the bar (Received: 847 tokens). Vercel AI SDK's useChat hook exposes isLoading and data that map cleanly to aria-busy and the counter respectively. Include an aria-live=polite region announcing key milestones (Streaming started, Streaming complete) so screen-reader users know when the response is finished.
How do I meet WCAG 2.2 AA for progress bars?
Five requirements from the AA level: (1) 1.4.3 contrast — the filled portion of the bar needs 3:1 contrast against the unfilled track, and any text label needs 4.5:1 against its background. (2) 1.4.1 use of color — never rely on color alone to communicate state; pair red / amber / green password-strength colors with icon plus text label (Strong — 4 of 4 criteria met). (3) 2.4.13 focus appearance — a 2px focus-visible outline with 3:1 contrast against adjacent colors on every interactive control (cancel, pause, play, step-next). (4) 4.1.2 name role value — every custom bar needs role=progressbar (or use native progress), aria-valuenow / aria-valuemin / aria-valuemax for the numeric value, and aria-label or aria-labelledby naming what is being measured (aria-label="File upload progress", not just aria-label="Progress"). (5) 2.3.3 animation from interactions — respect prefers-reduced-motion by disabling continuous animation loops (indeterminate stripe, pulse, sheen) and snapping bars to their end state instantly. Demo 09 is the reference implementation showing all five wired correctly with an inline annotation panel labeling each attribute.
How do I build a dark-mode adaptive progress bar with color-mix() tokens?
The traditional pattern needs two rule sets — one for the light theme, one for the dark theme — and they drift out of sync. With color-mix(in oklab, currentColor 15%, transparent) for the unfilled track and color-mix(in oklab, var(--accent) 90%, white 10%) for the fill, the bar reads correctly on both grounds without duplicate rules. On a light card currentColor is near-black so a 15% mix creates a soft grey track; on a dark card currentColor is near-white so the same 15% mix lifts a subtle white track from the dark surface. Wrap the token block in @supports (color:color-mix(in oklab, white 10%, transparent)) with fallback sRGB values declared first for engines that don't support the color-mix function. Pair with oklch() accent colors for perceptually uniform saturation across the light / dark theme swap — an accent authored as oklch(76% 0.16 295) stays vivid on both grounds where an sRGB #a78bfa washes out on a light background. Demo 08 is the technique primer showing both surfaces side-by-side with the same rules.
How do I keep progress bar animations at 60fps on mobile Safari?
Animate transform: scaleX() from 0 to 1 instead of width from 0% to 100% whenever possible — transform is a compositor-only property that stays on the GPU thread, while width triggers layout on every frame and jerks on mobile Safari with anything else running. Set transform-origin: left center and will-change: transform on the fill layer to give the browser a hint to promote it. Compositor pins also apply to opacity (for fading in the buffered range overlay) and filter (for the sheen shimmer on demo 16). Avoid animating background-position, box-shadow, and border-radius in continuous loops — they force layout or paint on every frame. For the ring / conic patterns (demos 13, 19) animate stroke-dashoffset (compositor-safe on SVG) or wrap the conic angle in @property --value with a syntax descriptor of the number type so it interpolates smoothly instead of jumping. Always pair motion with @media (prefers-reduced-motion: reduce) disabling transitions and snapping bars to end state.
How is this different from the other loading collections on codefronts?
Five loading / progress collections cover different search intent lanes. This collection — css-progress-bars — covers determinate and indeterminate progress UI as a value indicator (upload progress, form completion, password strength, download queue, video buffering, storage / quota meters). The bar communicates a specific value or a specific busy state that will resolve. css-scroll-progress-bar covers scroll-driven reading progress at the top of an article via animation-timeline: scroll() — the value is derived from scroll position, not a task. css-loader-designs covers generic loader shapes (spinners, dots, cubes) — the busy state without a value. css-loading-animations covers narrative branded loaders (chef, delivery truck, coffee brewing) — themed to the product domain. css-spinner-designs covers spinner-only patterns (arc, orbit, gradient conic sweep). css-skeleton-loaders covers content-placeholder skeleton UI showing the shape of upcoming content, not a value or busy state. Each collection ranks for its own set of Google queries.
How portable is the code across Tailwind, React, Vue, Svelte, and Astro?
Every demo is framework-agnostic vanilla CSS scoped under a single unique .pb-NN root so it drops into any framework without leaking. Tailwind v3 and v4 users can either drop the demo CSS into an @layer components block or apply the tokens in their own utility classes — the --value CSS custom property is Tailwind-native (style={{ '--value': 68 }} in React). React / Next.js / Remix consume the bar as JSX with style={{ '--value': progress }} inline styles; the demo's JS (upload progress, streaming, password strength, step tracker) can be lifted into a React hook — useUploadProgress, usePasswordStrength — with useState for the value and useEffect for the XHR / fetch setup. Vue 3 users wrap the demo in a template block with a style binding of the form :style set to an object mapping the --value custom property to the reactive progress ref. Svelte binds --value via style:--value={progress}. Astro islands (client:visible) work with the vanilla JS as-is. SolidJS, Qwik, and Angular signals all map to the same CSS custom property pattern.

Related collections

15 CSS 3D Tilt Hover Cards preview

15 CSS 3D Tilt Hover Cards

15 mouse-aware 3D tilt hover cards — e-commerce product spotlight, glassmorphism parallax team card, interactive pricing tier, holographic NFT collectible, pop-out mascot, dark tech grid with border glow, media player album art, blog article preview, cyberpunk neon glow, dashboard KPI widget, minimalist real estate, flip-to-back tilt, mobile app showcase, course learning card, and a Pure CSS touch-friendly tilt. Vanilla JS writes --rx/--ry/--mx/--my; all rendering stays in CSS on the GPU.

24 CSS Animated Cards preview

24 CSS Animated Cards

24 hand-coded CSS animated card patterns organised by what triggers the motion, not by card style. Scroll and entrance triggers: staggered grid reveals, @starting-style mount-in, scroll-driven scale and fade on animation-timeline: view(), View Transitions API card-to-detail morphs, blur-to-sharp lazy image loading, and FLIP re-layout when a filter changes. State-change triggers: add-to-cart success morph, like and save particle burst, expand and collapse to intrinsic height with calc-size(), swipe-to-dismiss on Pointer Events, and skeleton-to-loaded crossfade. Data-driven triggers: count-up KPI metrics, SVG sparkline draw-on-enter, and progress ring fill on load. Ambient idle loops: breathing glow, floating drift, animated gradient mesh backgrounds, conic rotating borders, auto-cycling testimonial decks, and live status pulses. Plus four hover patterns where the effect is the topic itself: cursor spotlight, holographic foil glare, corner ribbon slide, and horizontal accordion expand. Every card is scoped under a .ac-NN prefix for no-collision pasting, guards prefers-reduced-motion, animates compositor-only properties, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.

22 CSS Avatars preview

22 CSS Avatars

22 hand-coded CSS avatars for chat apps, team dashboards, comment threads, account menus, and social profiles. Covers circular and squircle shapes, gradient and conic story rings, hexagon clip-path crops, online status dots, notification count badges, verified checkmarks, stacked facepiles with plus-N overflow, initials fallbacks for users without photos, broken-image recovery, avatar pickers, and a size scale system where the ring, badge, and status dot all scale from a single custom property.

Search CodeFronts

Loading…