12 CSS Progress Bar Designs
A CSS progress bar is a visual indicator of how far a task or value has progressed toward a target — file uploads, multi-step forms, skill levels, fundraising goals, video buffer state, storage usage and more. These 12 hand-coded designs cover every common shape: linear bars, circular rings, conic gauges, segmented batteries, vertical goal meters and split stacked categories. Every demo uses semantic markup (real progress, fieldset, or labelled svg), modern CSS (@property animatable angles, conic gradients, stroke-dasharray with pathLength=100), and JavaScript only where a counter genuinely needs to stay in sync with a CSS transition.
Twelve premium CSS progress bar designs covering every real pattern — linear, circular, segmented, stacked, gauge, stepped, vertical and buffered. Built with semantic HTML, modern standards-based CSS (@property, :has(), conic gradients, prefers-reduced-motion), proper role="progressbar" and aria-valuenow attributes for accessibility, and JavaScript only where it adds real interaction. Each demo uses a different accent palette so you see the brand range at a glance — drop any of them straight into your project.
.pb-liquid {
position: relative;
width: 220px; height: 88px;
border-radius: 14px;
background: linear-gradient(180deg, #0f172a, #1e293b);
border: 1px solid rgba(56,189,248,0.25);
overflow: hidden;
box-shadow: 0 12px 30px -10px rgba(56,189,248,0.25);
font-family: system-ui, sans-serif;
}
.pb-liquid-fill {
position: absolute; left: 0; right: 0; bottom: 0;
height: var(--pb-liquid-level, 0%);
background: linear-gradient(180deg, rgba(56,189,248,0.85) 0%, rgba(14,165,233,0.95) 100%);
color: rgba(56,189,248,0.85); /* feeds the SVG waves */
transition: height 0.6s cubic-bezier(.5,0,.3,1.2);
}
.pb-liquid-wave {
position: absolute; left: 0; bottom: 100%;
width: 200%; height: 18px;
}
.pb-liquid-wave.w1 { animation: pbLiquidWave 4s linear infinite; opacity: 0.85; }
.pb-liquid-wave.w2 { animation: pbLiquidWave 6s linear infinite reverse; opacity: 0.55; }
@keyframes pbLiquidWave {
to { transform: translateX(-50%); }
}
.pb-liquid-label {
position: absolute; inset: 0;
display: grid; place-items: center;
color: #f0f9ff;
font-size: 14px;
letter-spacing: 0.04em;
text-shadow: 0 1px 4px rgba(0,0,0,0.45);
z-index: 1;
}
.pb-liquid-label strong { font-size: 20px; font-weight: 700; }
@media (prefers-reduced-motion: reduce) {
.pb-liquid-wave { animation: none; }
} <div class="pb-liquid" role="progressbar" aria-valuenow="68" aria-valuemin="0" aria-valuemax="100" aria-label="Storage used">
<span class="pb-liquid-fill" style="--pb-liquid-level: 68%;">
<svg class="pb-liquid-wave w1" viewBox="0 0 200 20" preserveAspectRatio="none" aria-hidden="true">
<path d="M0 10 Q 25 0 50 10 T 100 10 T 150 10 T 200 10 V 20 H 0 Z" fill="currentColor"/>
</svg>
<svg class="pb-liquid-wave w2" viewBox="0 0 200 20" preserveAspectRatio="none" aria-hidden="true">
<path d="M0 10 Q 25 20 50 10 T 100 10 T 150 10 T 200 10 V 20 H 0 Z" fill="currentColor"/>
</svg>
</span>
<span class="pb-liquid-label"><strong>68%</strong> storage used</span>
</div> - 1Cart
- 2Address
- 3Payment
- 4Confirm
.pb-step {
position: relative;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0;
width: 280px;
list-style: none;
margin: 0; padding: 0;
font-family: system-ui, sans-serif;
isolation: isolate;
}
.pb-step::before,
.pb-step::after {
content: ''; position: absolute;
left: 12.5%; right: 12.5%;
top: 15px; height: 2px;
border-radius: 2px;
z-index: 0;
}
.pb-step::before { background: rgba(255,255,255,0.08); }
.pb-step::after {
background: linear-gradient(90deg, #14b8a6, #2dd4bf);
width: 0; left: 12.5%; right: auto;
transition: width 0.5s cubic-bezier(.5,0,.3,1.2);
}
.pb-step[data-current="2"]::after { width: calc(((2 - 1) / (4 - 1)) * 75%); }
.pb-step[data-current="3"]::after { width: calc(((3 - 1) / (4 - 1)) * 75%); }
.pb-step[data-current="4"]::after { width: 75%; }
.pb-step-item {
position: relative;
display: grid; gap: 6px; place-items: center;
font-size: 11px; color: #94a3b8;
z-index: 1;
}
.pb-step-item span {
display: grid; place-items: center;
width: 32px; height: 32px;
border-radius: 50%;
background: #1e293b;
border: 2px solid rgba(255,255,255,0.08);
color: #94a3b8;
font-weight: 700; font-size: 13px;
transition: background 0.3s, border-color 0.3s, color 0.3s;
}
.pb-step-item em {
font-style: normal; font-size: 10.5px;
letter-spacing: 0.06em; text-transform: uppercase;
font-weight: 600;
}
.pb-step-item.is-done span,
.pb-step-item.is-current span {
background: linear-gradient(135deg, #14b8a6, #2dd4bf);
border-color: #2dd4bf;
color: #0f172a;
box-shadow: 0 0 0 4px rgba(20,184,166,0.15);
}
.pb-step-item.is-done span { font-size: 0; }
.pb-step-item.is-done span::before {
content: '';
width: 13px; height: 7px;
border: 2px solid #0f172a;
border-top: 0; border-right: 0;
transform: rotate(-45deg);
transform-origin: center;
}
.pb-step-item.is-current em { color: #2dd4bf; }
.pb-step-item.is-done em { color: #cbd5e1; } <ol class="pb-step" role="list" data-current="3"> <li class="pb-step-item is-done"><span>1</span><em>Cart</em></li> <li class="pb-step-item is-done"><span>2</span><em>Address</em></li> <li class="pb-step-item is-current"><span>3</span><em>Payment</em></li> <li class="pb-step-item"><span>4</span><em>Confirm</em></li> </ol>
@property --pb-ring-deg {
syntax: '<angle>'; inherits: false; initial-value: 0deg;
}
.pb-ring {
position: relative;
width: 120px; height: 120px;
border-radius: 50%;
--pb-ring-deg: calc(var(--pb-ring-value, 0) * 3.6deg);
background:
conic-gradient(
#c084fc 0deg,
#f472b6 var(--pb-ring-deg),
rgba(255,255,255,0.06) var(--pb-ring-deg),
rgba(255,255,255,0.06) 360deg
);
animation: pbRingDraw 1.4s cubic-bezier(.5,0,.3,1.2) forwards;
font-family: system-ui, sans-serif;
}
@keyframes pbRingDraw {
from { --pb-ring-deg: 0deg; }
to { --pb-ring-deg: calc(var(--pb-ring-value, 0) * 3.6deg); }
}
.pb-ring-track {
position: absolute; inset: 8px;
border-radius: 50%;
background: #15151d;
box-shadow: inset 0 0 18px rgba(192,132,252,0.15);
}
.pb-ring-meta {
position: absolute; inset: 0;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
gap: 4px;
line-height: 1;
}
.pb-ring-meta strong {
display: block;
font-size: 26px; font-weight: 700;
color: #f0eeff;
font-variant-numeric: tabular-nums;
line-height: 1;
letter-spacing: -0.02em;
}
.pb-ring-meta strong small {
font-size: 13px; font-weight: 600;
color: #c084fc; margin-left: 1px;
}
.pb-ring-meta em {
display: block;
font-style: normal;
font-size: 9.5px; font-weight: 600;
letter-spacing: 0.18em; text-transform: uppercase;
color: #a78bfa;
line-height: 1;
}
@media (prefers-reduced-motion: reduce) {
.pb-ring { animation: none; }
} <div class="pb-ring" role="progressbar" aria-valuenow="74" aria-valuemin="0" aria-valuemax="100" aria-label="Profile completion" style="--pb-ring-value: 74;">
<span class="pb-ring-track" aria-hidden="true"></span>
<span class="pb-ring-meta">
<strong>74<small>%</small></strong>
<em>Profile</em>
</span>
</div> .pb-seg {
width: 220px;
display: grid; gap: 6px;
font-family: system-ui, sans-serif;
}
.pb-seg-label {
font-size: 11px; font-weight: 600;
letter-spacing: 0.12em; text-transform: uppercase;
color: #94a3b8;
}
.pb-seg-rail {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 4px;
}
.pb-seg-cell {
height: 22px;
border-radius: 4px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.06);
position: relative;
overflow: hidden;
transition: background 0.4s ease;
}
.pb-seg[data-pb-seg="1"] .pb-seg-cell:nth-child(-n+1),
.pb-seg[data-pb-seg="2"] .pb-seg-cell:nth-child(-n+2),
.pb-seg[data-pb-seg="3"] .pb-seg-cell:nth-child(-n+3),
.pb-seg[data-pb-seg="4"] .pb-seg-cell:nth-child(-n+4),
.pb-seg[data-pb-seg="5"] .pb-seg-cell:nth-child(-n+5) {
background: linear-gradient(180deg, #34d399 0%, #10b981 100%);
border-color: #34d399;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.3);
}
.pb-seg[data-pb-seg="1"] .pb-seg-cell:nth-child(-n+1)::after,
.pb-seg[data-pb-seg="2"] .pb-seg-cell:nth-child(-n+2)::after,
.pb-seg[data-pb-seg="3"] .pb-seg-cell:nth-child(-n+3)::after,
.pb-seg[data-pb-seg="4"] .pb-seg-cell:nth-child(-n+4)::after,
.pb-seg[data-pb-seg="5"] .pb-seg-cell:nth-child(-n+5)::after {
content: ''; position: absolute;
left: 0; right: 0; top: 0; height: 40%;
background: linear-gradient(180deg, rgba(255,255,255,0.25), transparent);
}
.pb-seg-value {
font-size: 12px; color: #6ee7b7;
font-family: 'JetBrains Mono', monospace;
letter-spacing: 0.04em;
} <div class="pb-seg" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" aria-label="Network signal" data-pb-seg="3" data-pb-seg-total="5">
<span class="pb-seg-label">Signal</span>
<span class="pb-seg-rail" aria-hidden="true">
<span class="pb-seg-cell"></span>
<span class="pb-seg-cell"></span>
<span class="pb-seg-cell"></span>
<span class="pb-seg-cell"></span>
<span class="pb-seg-cell"></span>
</span>
<span class="pb-seg-value">3 / 5 bars</span>
</div> .pb-stripe {
width: 240px;
display: grid; gap: 6px;
font-family: system-ui, sans-serif;
}
.pb-stripe-head {
display: flex; justify-content: space-between; align-items: center;
font-size: 12px; font-weight: 600;
color: #fde68a;
font-family: 'JetBrains Mono', monospace;
letter-spacing: 0.04em;
}
.pb-stripe-pct {
background: rgba(245,158,11,0.15);
color: #fbbf24;
padding: 2px 8px; border-radius: 4px;
font-variant-numeric: tabular-nums;
}
.pb-stripe-rail {
height: 14px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(245,158,11,0.18);
border-radius: 7px;
overflow: hidden;
}
.pb-stripe-fill {
width: var(--pb-stripe-w, 0%);
height: 100%;
background:
repeating-linear-gradient(
-45deg,
#f59e0b 0,
#f59e0b 8px,
#fbbf24 8px,
#fbbf24 16px
);
background-size: 22px 22px;
animation: pbStripeMove 0.8s linear infinite;
transition: width 0.5s cubic-bezier(.5,0,.3,1.2);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.15);
}
@keyframes pbStripeMove {
from { background-position: 0 0; }
to { background-position: 44px 0; }
}
.pb-stripe-foot {
font-size: 11px; color: #94a3b8;
letter-spacing: 0.02em;
}
@media (prefers-reduced-motion: reduce) {
.pb-stripe-fill { animation: none; }
} <div class="pb-stripe" role="progressbar" aria-valuenow="62" aria-valuemin="0" aria-valuemax="100" aria-label="Build progress">
<div class="pb-stripe-head">
<span>Build #482</span>
<span class="pb-stripe-pct">62%</span>
</div>
<div class="pb-stripe-rail">
<div class="pb-stripe-fill" style="--pb-stripe-w: 62%;"></div>
</div>
<div class="pb-stripe-foot">Compiling assets · 4.2 MB / 6.8 MB</div>
</div> .pb-pulse {
width: 260px;
display: grid; gap: 8px;
font-family: system-ui, sans-serif;
}
.pb-pulse-head {
display: flex; justify-content: space-between; align-items: baseline;
color: #f0eeff;
font-size: 13px;
}
.pb-pulse-head strong { font-weight: 600; }
.pb-pulse-pct {
font-family: 'JetBrains Mono', monospace;
color: #c4b5fd;
font-size: 12px; font-weight: 700;
font-variant-numeric: tabular-nums;
}
.pb-pulse-rail {
height: 6px;
background: rgba(255,255,255,0.06);
border-radius: 99px;
overflow: hidden;
position: relative;
}
.pb-pulse-fill {
position: relative;
width: var(--pb-pulse-w, 0%);
height: 100%;
background: linear-gradient(90deg,
rgba(124,108,255,0.25) 0%,
#7c6cff 60%,
#c4b5fd 100%);
border-radius: inherit;
transition: width 0.5s cubic-bezier(.5,0,.3,1.2);
}
.pb-pulse-edge {
position: absolute; right: -4px; top: -2px; bottom: -2px;
width: 10px;
background: radial-gradient(circle, #fff 0%, rgba(196,181,253,0.55) 60%, transparent 80%);
border-radius: 50%;
animation: pbPulseGlow 1.4s ease-in-out infinite;
}
@keyframes pbPulseGlow {
0%, 100% { opacity: 0.6; transform: scale(1); }
50% { opacity: 1; transform: scale(1.25); }
}
@media (prefers-reduced-motion: reduce) {
.pb-pulse-edge { animation: none; }
} <div class="pb-pulse" role="progressbar" aria-valuenow="48" aria-valuemin="0" aria-valuemax="100" aria-label="Sync progress">
<div class="pb-pulse-head">
<strong>Syncing your workspace</strong>
<span class="pb-pulse-pct">48%</span>
</div>
<div class="pb-pulse-rail">
<div class="pb-pulse-fill" style="--pb-pulse-w: 48%;">
<span class="pb-pulse-edge" aria-hidden="true"></span>
</div>
</div>
</div> .pb-goal {
width: 200px; height: 200px;
display: grid;
grid-template-columns: 50px 1fr;
gap: 16px;
font-family: system-ui, sans-serif;
}
.pb-goal-track {
position: relative;
width: 12px; height: 100%;
margin-left: 18px;
background: rgba(255,255,255,0.06);
border-radius: 6px;
overflow: visible;
}
.pb-goal-fill {
position: absolute; left: 0; right: 0; bottom: 0;
height: var(--pb-goal-pct, 0%);
background: linear-gradient(180deg, #ec4899, #f43f5e);
border-radius: 6px;
box-shadow: 0 0 18px rgba(236,72,153,0.5);
transition: height 0.7s cubic-bezier(.5,0,.3,1.2);
}
.pb-goal-marks {
position: absolute; inset: 0;
list-style: none; margin: 0; padding: 0;
}
.pb-goal-marks li {
position: absolute; right: -28px;
bottom: var(--m);
transform: translateY(50%);
font-size: 10px; font-weight: 700;
color: #475569;
letter-spacing: 0.04em;
font-family: 'JetBrains Mono', monospace;
display: flex; align-items: center; gap: 6px;
transition: color 0.3s;
}
.pb-goal-marks li::before {
content: '';
width: 8px; height: 8px;
border-radius: 50%;
background: #1e293b;
border: 2px solid #475569;
margin-left: -28px;
transition: background 0.3s, border-color 0.3s, box-shadow 0.3s;
}
.pb-goal-marks li.hit { color: #fbcfe8; }
.pb-goal-marks li.hit::before {
background: #ec4899;
border-color: #f9a8d4;
box-shadow: 0 0 10px rgba(236,72,153,0.7);
}
.pb-goal-meta {
display: flex; flex-direction: column; justify-content: flex-end;
gap: 2px;
color: #f0eeff;
}
.pb-goal-meta strong {
font-size: 22px; font-weight: 700;
color: #fda4af;
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
}
.pb-goal-meta span { font-size: 11px; color: #94a3b8; }
.pb-goal-meta em { font-style: normal; color: #fda4af; font-weight: 600; } <div class="pb-goal" role="progressbar" aria-valuenow="6800" aria-valuemin="0" aria-valuemax="10000" aria-label="Funds raised" style="--pb-goal-pct: 68%;">
<div class="pb-goal-track">
<div class="pb-goal-fill"></div>
<ul class="pb-goal-marks" aria-hidden="true">
<li style="--m: 100%;"><span>$10k</span></li>
<li style="--m: 50%;" class="hit"><span>$5k</span></li>
<li style="--m: 10%;" class="hit"><span>$1k</span></li>
</ul>
</div>
<div class="pb-goal-meta">
<strong>$6,800</strong>
<span>raised of <em>$10,000</em></span>
</div>
</div> - TypeScript
- 92%
- React
- 88%
- CSS & Sass
- 95%
- Figma
- 78%
.pb-skill {
width: 260px;
margin: 0; padding: 0;
display: grid; gap: 10px;
font-family: system-ui, sans-serif;
}
.pb-skill-row {
display: grid;
grid-template-columns: 90px 1fr;
align-items: center;
gap: 10px;
}
.pb-skill dt {
font-size: 11.5px; font-weight: 600;
color: #fef3c7;
letter-spacing: 0.02em;
}
.pb-skill dd {
margin: 0;
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 8px;
}
.pb-skill-rail {
display: block;
height: 6px;
background: rgba(255,255,255,0.05);
border-radius: 99px;
overflow: hidden;
}
.pb-skill-fill {
display: block;
height: 100%; width: 0;
background: linear-gradient(90deg, #f97316, #fbbf24);
border-radius: inherit;
animation: pbSkillSlide 1s cubic-bezier(.5,0,.3,1.2) var(--pb-skill-delay, 0s) forwards;
}
@keyframes pbSkillSlide {
to { width: var(--pb-skill-w, 0%); }
}
.pb-skill-pct {
font-family: 'JetBrains Mono', monospace;
font-size: 11px; font-weight: 700;
color: #fbbf24;
font-variant-numeric: tabular-nums;
min-width: 30px; text-align: right;
}
@media (prefers-reduced-motion: reduce) {
.pb-skill-fill { animation: none; width: var(--pb-skill-w, 0%); }
} <dl class="pb-skill">
<div class="pb-skill-row">
<dt>TypeScript</dt>
<dd>
<span class="pb-skill-rail">
<span class="pb-skill-fill" style="--pb-skill-w: 92%;"></span>
</span>
<span class="pb-skill-pct">92%</span>
</dd>
</div>
<div class="pb-skill-row">
<dt>React</dt>
<dd>
<span class="pb-skill-rail">
<span class="pb-skill-fill" style="--pb-skill-w: 88%; --pb-skill-delay: .1s;"></span>
</span>
<span class="pb-skill-pct">88%</span>
</dd>
</div>
<div class="pb-skill-row">
<dt>CSS & Sass</dt>
<dd>
<span class="pb-skill-rail">
<span class="pb-skill-fill" style="--pb-skill-w: 95%; --pb-skill-delay: .2s;"></span>
</span>
<span class="pb-skill-pct">95%</span>
</dd>
</div>
<div class="pb-skill-row">
<dt>Figma</dt>
<dd>
<span class="pb-skill-rail">
<span class="pb-skill-fill" style="--pb-skill-w: 78%; --pb-skill-delay: .3s;"></span>
</span>
<span class="pb-skill-pct">78%</span>
</dd>
</div>
</dl> .pb-count {
position: relative;
width: 130px; height: 130px;
font-family: system-ui, sans-serif;
}
.pb-count-ring {
width: 100%; height: 100%;
transform: rotate(-90deg);
}
.pb-count-bg,
.pb-count-prog {
fill: none;
stroke-width: 7;
stroke-linecap: round;
}
.pb-count-bg { stroke: rgba(255,255,255,0.06); }
.pb-count-prog {
stroke: url(#pb-count-grad);
stroke-dasharray: 100;
stroke-dashoffset: 100;
transition: stroke-dashoffset 1.4s cubic-bezier(.5,0,.3,1.2);
}
.pb-count.is-ready .pb-count-prog {
stroke-dashoffset: calc(100 - (var(--pb-count-pct, 0) * 100));
}
.pb-count-meta {
position: absolute; inset: 0;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
gap: 4px;
line-height: 1;
}
.pb-count-meta strong {
display: block;
font-size: 28px; font-weight: 700;
color: #f0eeff;
font-variant-numeric: tabular-nums;
line-height: 1;
letter-spacing: -0.02em;
}
.pb-count-meta span {
display: block;
font-size: 9.5px; font-weight: 600;
color: #34d399;
letter-spacing: 0.18em; text-transform: uppercase;
line-height: 1;
} <div class="pb-count" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-label="Score" data-pb-count="86">
<svg class="pb-count-ring" viewBox="0 0 100 100" aria-hidden="true">
<defs>
<linearGradient id="pb-count-grad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#34d399"/>
<stop offset="100%" stop-color="#10b981"/>
</linearGradient>
</defs>
<circle class="pb-count-bg" cx="50" cy="50" r="42"></circle>
<circle class="pb-count-prog" cx="50" cy="50" r="42" pathLength="100"></circle>
</svg>
<div class="pb-count-meta">
<strong data-pb-count-num>0</strong>
<span>score</span>
</div>
</div> // Reads the target value from data-pb-count and animates the
// SVG ring + centre number from 0 to that value over 1.4s.
// Drop this on every page where you render a .pb-count element.
document.querySelectorAll('[data-pb-count]').forEach(function (el) {
var target = Number(el.dataset.pbCount) || 0;
var num = el.querySelector('[data-pb-count-num]');
// Drive the ring's stroke-dashoffset via a CSS custom property
el.style.setProperty('--pb-count-pct', String(target / 100));
requestAnimationFrame(function () { el.classList.add('is-ready'); });
// Tick the centre number in sync with the 1.4s ring transition
var start = null;
var duration = 1400;
function tick(t) {
if (start === null) start = t;
var p = Math.min(1, (t - start) / duration);
var eased = 1 - Math.pow(1 - p, 3); // ease-out cubic
var v = Math.round(target * eased);
if (num) num.textContent = v;
el.setAttribute('aria-valuenow', String(v));
if (p < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}); - Documents 36 GB
- Photos 23 GB
- Apps 18 GB
- Free 51 GB
.pb-stack {
width: 260px;
display: grid; gap: 10px;
font-family: system-ui, sans-serif;
}
.pb-stack-head {
display: flex; align-items: baseline; justify-content: space-between;
color: #f0eeff;
}
.pb-stack-head strong {
font-size: 18px; font-weight: 700;
letter-spacing: -0.01em;
}
.pb-stack-head span { font-size: 12px; color: #94a3b8; }
.pb-stack-rail {
display: flex; height: 10px;
background: rgba(255,255,255,0.05);
border-radius: 99px;
overflow: hidden;
gap: 2px;
padding: 0 1px;
}
.pb-stack-seg {
height: 100%;
border-radius: 99px;
transition: width 0.6s cubic-bezier(.5,0,.3,1.2);
width: var(--w, 0%);
}
.pb-stack-seg.s1 { background: linear-gradient(90deg, #6366f1, #818cf8); }
.pb-stack-seg.s2 { background: linear-gradient(90deg, #ec4899, #f472b6); }
.pb-stack-seg.s3 { background: linear-gradient(90deg, #14b8a6, #2dd4bf); }
.pb-stack-seg.s4 { background: rgba(255,255,255,0.12); }
.pb-stack-legend {
list-style: none; margin: 0; padding: 0;
display: grid; grid-template-columns: 1fr 1fr; gap: 6px 14px;
font-size: 11px;
color: #cbd5e1;
}
.pb-stack-legend li {
display: flex; align-items: center; gap: 6px;
}
.pb-stack-legend li i {
width: 8px; height: 8px;
border-radius: 2px;
display: inline-block;
}
.pb-stack-legend li.s1 i { background: #818cf8; }
.pb-stack-legend li.s2 i { background: #f472b6; }
.pb-stack-legend li.s3 i { background: #2dd4bf; }
.pb-stack-legend li.s4 i { background: rgba(255,255,255,0.3); }
.pb-stack-legend em {
font-style: normal; margin-left: auto;
color: #94a3b8;
font-family: 'JetBrains Mono', monospace;
font-size: 10.5px;
} <div class="pb-stack" role="img" aria-label="Storage usage breakdown">
<div class="pb-stack-head">
<strong>128 GB used</strong>
<span>of 256 GB</span>
</div>
<div class="pb-stack-rail">
<span class="pb-stack-seg s1" style="--w: 28%;" aria-label="Documents 28%"></span>
<span class="pb-stack-seg s2" style="--w: 18%;" aria-label="Photos 18%"></span>
<span class="pb-stack-seg s3" style="--w: 14%;" aria-label="Apps 14%"></span>
<span class="pb-stack-seg s4" style="--w: 40%;" aria-label="Free 40%"></span>
</div>
<ul class="pb-stack-legend">
<li class="s1"><i></i>Documents <em>36 GB</em></li>
<li class="s2"><i></i>Photos <em>23 GB</em></li>
<li class="s3"><i></i>Apps <em>18 GB</em></li>
<li class="s4"><i></i>Free <em>51 GB</em></li>
</ul>
</div> .pb-gauge {
width: 220px; height: 170px;
font-family: system-ui, sans-serif;
}
.pb-gauge-svg {
width: 100%; height: 100%;
display: block;
overflow: visible;
}
.pb-gauge-value {
stroke-dasharray: 100;
stroke-dashoffset: 100;
animation: pbGaugeArc 1.4s cubic-bezier(.5,0,.3,1.2) forwards;
}
@keyframes pbGaugeArc {
to { stroke-dashoffset: calc(100 - var(--pb-gauge-value, 0)); }
}
.pb-gauge-needle {
transform-origin: 100px 105px;
animation: pbGaugeSweep 1.4s cubic-bezier(.5,0,.3,1.2) forwards;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
}
@keyframes pbGaugeSweep {
from { transform: rotate(-90deg); }
to { transform: rotate(calc(var(--pb-gauge-value, 0) * 1.8deg - 90deg)); }
}
.pb-gauge-tick {
font-family: 'JetBrains Mono', monospace;
font-size: 10px; font-weight: 700;
fill: #64748b;
letter-spacing: 0.04em;
}
.pb-gauge-num {
font-family: system-ui, sans-serif;
font-size: 30px; font-weight: 700;
fill: #34d399;
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
}
.pb-gauge-label {
font-family: system-ui, sans-serif;
font-size: 10px; font-weight: 600;
fill: #94a3b8;
letter-spacing: 0.16em;
}
@media (prefers-reduced-motion: reduce) {
.pb-gauge-value { animation: none; stroke-dashoffset: calc(100 - var(--pb-gauge-value, 0)); }
.pb-gauge-needle { animation: none; transform: rotate(calc(var(--pb-gauge-value, 0) * 1.8deg - 90deg)); }
} <div class="pb-gauge" role="progressbar" aria-valuenow="78" aria-valuemin="0" aria-valuemax="100" aria-label="Performance score" style="--pb-gauge-value: 78;">
<svg class="pb-gauge-svg" viewBox="0 0 200 170" aria-hidden="true">
<defs>
<linearGradient id="pb-gauge-grad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#f43f5e"/>
<stop offset="50%" stop-color="#fb923c"/>
<stop offset="100%" stop-color="#34d399"/>
</linearGradient>
</defs>
<!-- track -->
<path class="pb-gauge-track" d="M 25 105 A 75 75 0 0 1 175 105"
fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="12" stroke-linecap="round"/>
<!-- value arc -->
<path class="pb-gauge-value" d="M 25 105 A 75 75 0 0 1 175 105"
fill="none" stroke="url(#pb-gauge-grad)" stroke-width="12" stroke-linecap="round"
pathLength="100"/>
<!-- ticks -->
<text class="pb-gauge-tick" x="25" y="125" text-anchor="middle">0</text>
<text class="pb-gauge-tick" x="100" y="22" text-anchor="middle">50</text>
<text class="pb-gauge-tick" x="175" y="125" text-anchor="middle">100</text>
<!-- needle -->
<g class="pb-gauge-needle">
<line x1="100" y1="105" x2="100" y2="44" stroke="#f0eeff" stroke-width="3" stroke-linecap="round"/>
<circle cx="100" cy="105" r="7" fill="#fff" stroke="#15151d" stroke-width="2"/>
</g>
<!-- score readout -->
<text class="pb-gauge-num" x="100" y="148" text-anchor="middle">78</text>
<text class="pb-gauge-label" x="100" y="164" text-anchor="middle">PERFORMANCE</text>
</svg>
</div> .pb-buf {
width: 280px;
display: grid; gap: 8px;
font-family: system-ui, sans-serif;
}
.pb-buf-time {
display: flex; justify-content: space-between;
font-family: 'JetBrains Mono', monospace;
font-size: 11px; color: #f0eeff;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.pb-buf-total { color: #94a3b8; }
.pb-buf-rail {
position: relative;
height: 5px;
background: rgba(255,255,255,0.08);
border-radius: 99px;
}
.pb-buf-buffered {
position: absolute; inset: 0;
width: var(--pb-buf-buffered, 0%);
background: rgba(255,255,255,0.18);
border-radius: inherit;
transition: width 0.4s ease;
}
.pb-buf-played {
position: absolute; inset: 0;
width: var(--pb-buf-played, 0%);
background: linear-gradient(90deg, #ef4444, #f97316);
border-radius: inherit;
transition: width 0.2s linear;
}
.pb-buf-thumb {
position: absolute; top: 50%;
left: var(--pb-buf-played, 0%);
width: 12px; height: 12px;
margin-left: -6px; margin-top: -6px;
background: #fff;
border-radius: 50%;
box-shadow:
0 0 0 3px rgba(239,68,68,0.4),
0 2px 8px rgba(0,0,0,0.5);
transform: scale(0);
transition: transform 0.2s ease, left 0.2s linear;
}
.pb-buf-rail:hover .pb-buf-thumb,
.pb-buf:focus-within .pb-buf-thumb { transform: scale(1); }
.pb-buf-meta {
display: flex; justify-content: space-between; align-items: center;
}
.pb-buf-title {
font-size: 12px; font-weight: 600;
color: #f0eeff;
}
.pb-buf-quality {
font-size: 10px; color: #94a3b8;
font-weight: 600;
background: rgba(255,255,255,0.06);
padding: 2px 6px; border-radius: 4px;
letter-spacing: 0.04em;
} <div class="pb-buf" role="progressbar" aria-valuenow="42" aria-valuemin="0" aria-valuemax="100" aria-label="Playback progress">
<div class="pb-buf-time">
<span>1:24</span>
<span class="pb-buf-total">4:18</span>
</div>
<div class="pb-buf-rail">
<span class="pb-buf-buffered" style="--pb-buf-buffered: 68%;"></span>
<span class="pb-buf-played" style="--pb-buf-played: 42%;"></span>
<span class="pb-buf-thumb" style="--pb-buf-played: 42%;"></span>
</div>
<div class="pb-buf-meta">
<span class="pb-buf-title">Penthouse Tour · 4K</span>
<span class="pb-buf-quality">HD · 1080p</span>
</div>
</div> Frequently asked questions
What is a CSS progress bar?
Should I use the native HTML progress element?
How do I make a circular CSS progress bar?
Are these progress bars accessible?
Do these progress bars need JavaScript?
Related collections
22 CSS Button Group Designs
22 hand-coded CSS button group designs — segmented pill, connected outline, filter chips, split action, toggle, pagination, stepper wizard, view switcher, FAB, brutalist, glass, neon, date range, approve / reject, icon toolbar, dropdown combo, action group, number stepper, tab nav, aurora drift. Semantic HTML, accessible, copy-paste ready.
31 CSS Buttons
31 hand-coded CSS buttons — gradients, glassmorphism, 3D press, neon glow, ripple, glitch, shimmer, rainbow border and more. Pure CSS, copy-paste ready.
20 CSS Cards with Animations
20 hand-crafted CSS card components — aurora glow, 3D tilt, glassmorphism, neon borders, flip card, pricing, terminal, music player, weather widget and more.