20 CSS Loaders 09 / 20
CSS Circular Progress Loader
Four SVG-based circular progress loaders — a stroke-dashoffset arc, segmented arcs, a gauge semicircle, and radial concentric rings — covering all common circular-indicator design patterns.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="ld-09">
<div class="ld-09__stage">
<div class="ld-09__cell">
<div class="ld-09__svg-wrap">
<svg viewBox="0 0 90 90"><circle class="ld-09__track" cx="45" cy="45" r="36"/><circle class="ld-09__prog" cx="45" cy="45" r="36"/></svg>
<div class="ld-09__pct">78%</div>
</div> <div class="ld-09">
<div class="ld-09__stage">
<div class="ld-09__cell">
<div class="ld-09__svg-wrap">
<svg viewBox="0 0 90 90"><circle class="ld-09__track" cx="45" cy="45" r="36"/><circle class="ld-09__prog" cx="45" cy="45" r="36"/></svg>
<div class="ld-09__pct">78%</div>
</div>.ld-09,.ld-09 *,.ld-09 *::before,.ld-09 *::after{box-sizing:border-box;margin:0;padding:0}
.ld-09{
--bg:#0f172a;--c1:#818cf8;--c2:#34d399;--c3:#fb923c;--c4:#f472b6;
background:var(--bg);display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:'Segoe UI',sans-serif;
}
.ld-09__stage{display:flex;gap:48px;flex-wrap:wrap;justify-content:center;padding:40px}
.ld-09__cell{display:flex;flex-direction:column;align-items:center;gap:16px}
.ld-09__label{color:rgba(255,255,255,.4);font-size:11px;letter-spacing:1.5px;text-transform:uppercase}
/* SVG Circle progress */
.ld-09__svg-wrap{position:relative;width:90px;height:90px}
.ld-09__svg-wrap svg{width:90px;height:90px;transform:rotate(-90deg)}
.ld-09__track{fill:none;stroke:rgba(255,255,255,.07);stroke-width:6}
.ld-09__prog{fill:none;stroke:var(--c1);stroke-width:6;stroke-linecap:round;stroke-dasharray:226;animation:ld-09-dash 2s ease-in-out infinite}
@keyframes ld-09-dash{0%{stroke-dashoffset:226;opacity:.6}50%{stroke-dashoffset:50;opacity:1}100%{stroke-dashoffset:226;opacity:.6}}
.ld-09__pct{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:var(--c1)}
/* Segmented ring */
.ld-09__seg-ring{width:90px;height:90px;border-radius:50%;position:relative}
.ld-09__seg-ring svg{width:90px;height:90px}
.ld-09__seg-arc{fill:none;stroke-width:6;stroke-linecap:round}
.ld-09__seg-arc:nth-child(1){stroke:var(--c2);stroke-dasharray:40 226;stroke-dashoffset:0;animation:ld-09-seg1 3s linear infinite}
.ld-09__seg-arc:nth-child(2){stroke:var(--c3);stroke-dasharray:40 226;stroke-dashoffset:-60;animation:ld-09-seg2 3s linear infinite}
.ld-09__seg-arc:nth-child(3){stroke:var(--c4);stroke-dasharray:40 226;stroke-dashoffset:-120;animation:ld-09-seg3 3s linear infinite}
@keyframes ld-09-seg1{to{stroke-dashoffset:-226}}
@keyframes ld-09-seg2{to{stroke-dashoffset:-286}}
@keyframes ld-09-seg3{to{stroke-dashoffset:-346}}
/* Gauge */
.ld-09__gauge{width:100px;height:55px;position:relative;overflow:hidden}
.ld-09__gauge svg{width:100px;height:100px;transform:translateY(10px)}
.ld-09__gauge-track{fill:none;stroke:rgba(255,255,255,.07);stroke-width:8;stroke-dasharray:125;stroke-dashoffset:-62.5}
.ld-09__gauge-fill{fill:none;stroke:var(--c3);stroke-width:8;stroke-linecap:round;stroke-dasharray:125;stroke-dashoffset:-62.5;animation:ld-09-gauge 2.5s ease-in-out infinite}
@keyframes ld-09-gauge{0%{stroke-dashoffset:0}50%{stroke-dashoffset:-62.5}100%{stroke-dashoffset:0}}
/* Radial multi */
.ld-09__radial{width:90px;height:90px;position:relative}
.ld-09__radial svg{width:90px;height:90px;transform:rotate(-90deg)}
.ld-09__r-track{fill:none;stroke:rgba(255,255,255,.05);stroke-linecap:round}
.ld-09__r-fill{fill:none;stroke-linecap:round;animation:ld-09-rdash 2s ease-in-out infinite}
.ld-09__r-fill:nth-child(2){stroke:var(--c1);stroke-width:5;stroke-dasharray:200;animation-delay:0s}
.ld-09__r-fill:nth-child(4){stroke:var(--c2);stroke-width:5;stroke-dasharray:160;animation-delay:.3s}
.ld-09__r-fill:nth-child(6){stroke:var(--c3);stroke-width:5;stroke-dasharray:120;animation-delay:.6s}
@keyframes ld-09-rdash{0%{stroke-dashoffset:200;opacity:.5}60%{stroke-dashoffset:40;opacity:1}100%{stroke-dashoffset:200;opacity:.5}}
@media(prefers-reduced-motion:reduce){
.ld-09__prog,.ld-09__seg-arc,.ld-09__gauge-fill,.ld-09__r-fill{animation:none}
} .ld-09,.ld-09 *,.ld-09 *::before,.ld-09 *::after{box-sizing:border-box;margin:0;padding:0}
.ld-09{
--bg:#0f172a;--c1:#818cf8;--c2:#34d399;--c3:#fb923c;--c4:#f472b6;
background:var(--bg);display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:'Segoe UI',sans-serif;
}
.ld-09__stage{display:flex;gap:48px;flex-wrap:wrap;justify-content:center;padding:40px}
.ld-09__cell{display:flex;flex-direction:column;align-items:center;gap:16px}
.ld-09__label{color:rgba(255,255,255,.4);font-size:11px;letter-spacing:1.5px;text-transform:uppercase}
/* SVG Circle progress */
.ld-09__svg-wrap{position:relative;width:90px;height:90px}
.ld-09__svg-wrap svg{width:90px;height:90px;transform:rotate(-90deg)}
.ld-09__track{fill:none;stroke:rgba(255,255,255,.07);stroke-width:6}
.ld-09__prog{fill:none;stroke:var(--c1);stroke-width:6;stroke-linecap:round;stroke-dasharray:226;animation:ld-09-dash 2s ease-in-out infinite}
@keyframes ld-09-dash{0%{stroke-dashoffset:226;opacity:.6}50%{stroke-dashoffset:50;opacity:1}100%{stroke-dashoffset:226;opacity:.6}}
.ld-09__pct{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:var(--c1)}
/* Segmented ring */
.ld-09__seg-ring{width:90px;height:90px;border-radius:50%;position:relative}
.ld-09__seg-ring svg{width:90px;height:90px}
.ld-09__seg-arc{fill:none;stroke-width:6;stroke-linecap:round}
.ld-09__seg-arc:nth-child(1){stroke:var(--c2);stroke-dasharray:40 226;stroke-dashoffset:0;animation:ld-09-seg1 3s linear infinite}
.ld-09__seg-arc:nth-child(2){stroke:var(--c3);stroke-dasharray:40 226;stroke-dashoffset:-60;animation:ld-09-seg2 3s linear infinite}
.ld-09__seg-arc:nth-child(3){stroke:var(--c4);stroke-dasharray:40 226;stroke-dashoffset:-120;animation:ld-09-seg3 3s linear infinite}
@keyframes ld-09-seg1{to{stroke-dashoffset:-226}}
@keyframes ld-09-seg2{to{stroke-dashoffset:-286}}
@keyframes ld-09-seg3{to{stroke-dashoffset:-346}}
/* Gauge */
.ld-09__gauge{width:100px;height:55px;position:relative;overflow:hidden}
.ld-09__gauge svg{width:100px;height:100px;transform:translateY(10px)}
.ld-09__gauge-track{fill:none;stroke:rgba(255,255,255,.07);stroke-width:8;stroke-dasharray:125;stroke-dashoffset:-62.5}
.ld-09__gauge-fill{fill:none;stroke:var(--c3);stroke-width:8;stroke-linecap:round;stroke-dasharray:125;stroke-dashoffset:-62.5;animation:ld-09-gauge 2.5s ease-in-out infinite}
@keyframes ld-09-gauge{0%{stroke-dashoffset:0}50%{stroke-dashoffset:-62.5}100%{stroke-dashoffset:0}}
/* Radial multi */
.ld-09__radial{width:90px;height:90px;position:relative}
.ld-09__radial svg{width:90px;height:90px;transform:rotate(-90deg)}
.ld-09__r-track{fill:none;stroke:rgba(255,255,255,.05);stroke-linecap:round}
.ld-09__r-fill{fill:none;stroke-linecap:round;animation:ld-09-rdash 2s ease-in-out infinite}
.ld-09__r-fill:nth-child(2){stroke:var(--c1);stroke-width:5;stroke-dasharray:200;animation-delay:0s}
.ld-09__r-fill:nth-child(4){stroke:var(--c2);stroke-width:5;stroke-dasharray:160;animation-delay:.3s}
.ld-09__r-fill:nth-child(6){stroke:var(--c3);stroke-width:5;stroke-dasharray:120;animation-delay:.6s}
@keyframes ld-09-rdash{0%{stroke-dashoffset:200;opacity:.5}60%{stroke-dashoffset:40;opacity:1}100%{stroke-dashoffset:200;opacity:.5}}
@media(prefers-reduced-motion:reduce){
.ld-09__prog,.ld-09__seg-arc,.ld-09__gauge-fill,.ld-09__r-fill{animation:none}
}How this works
All circular loaders use SVG <circle> elements with stroke-dasharray and stroke-dashoffset rather than CSS borders, giving sub-pixel arc precision at any size. The main arc sets stroke-dasharray: 226 (the circumference of a 36-radius circle: 2π × 36 ≈ 226px) and animates stroke-dashoffset from 226 (empty) to 50 (78% filled) and back with ease-in-out. The SVG container is rotated -90deg so the arc starts from the top.
The segmented loader places three arcs with stroke-dasharray: 40 226 — creating a 40px arc followed by a transparent gap — and animates all three's stroke-dashoffset continuously downward at different speeds to create a spinning multi-segment effect. The radial concentric variant stacks three rings at radii 38, 30, and 22 with independent stroke-dashoffset animations and staggered delays for a layered depth effect.
Customize
- Change the track circle colour from
rgba(255,255,255,.07)to a brand colour at low opacity to create coloured track rails. - Resize any arc by changing the SVG
rattribute and updatingstroke-dasharrayto2 * Math.PI * r— keep the viewBox matching. - Add a percentage text overlay inside
.ld-09__svg-wrapby positioning adivabsolutely withinset:0; display:flex; align-items:center; justify-content:center. - Adjust stroke thickness via
stroke-widthon the<circle>— values above12create a chunky gauge look;2gives a hairline style. - Convert the indeterminate loader to determinate by replacing the keyframe with a CSS custom property driven via JS:
style.setProperty('--progress', value).
Watch out for
- The SVG
transform: rotate(-90deg)on the container only works if the SVG preserves its intrinsic dimensions — explicitly setwidthandheightattributes on the<svg>element. stroke-dasharrayvalues must match the circle's circumference exactly (2πr); a mismatched value produces a gap at the arc end-point even at 100% fill.- The gauge semicircle uses
stroke-dashoffset: -62.5as its base offset to position the arc at the bottom half — changingrrequires recalculating this offset.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 49+ | 9+ | 44+ | 49+ |
SVG stroke-dashoffset animation is universally supported across all modern browsers.