20 CSS Loaders09 / 20

Pure CSSMIT licensed

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.

Published

Live Demo
Try it

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>
.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;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Loader from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Circular Progress Loader
Source: https://codefronts.com/motion/css-loader-designs/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.
## HTML
```html
<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>
```
## CSS
```css
.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.

Make it yours

  • 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 r attribute and updating stroke-dasharray to 2 * Math.PI * r — keep the viewBox matching.
  • Add a percentage text overlay inside .ld-09__svg-wrap by positioning a div absolutely with inset:0; display:flex; align-items:center; justify-content:center.
  • Adjust stroke thickness via stroke-width on the <circle> — values above 12 create a chunky gauge look; 2 gives 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).

Gotchas — read before shipping

  • The SVG transform: rotate(-90deg) on the container only works if the SVG preserves its intrinsic dimensions — explicitly set width and height attributes on the <svg> element.
  • stroke-dasharray values 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.5 as its base offset to position the arc at the bottom half — changing r requires recalculating this offset.

Browser support

ChromeSafariFirefoxEdge
49+9+44+49+

SVG stroke-dashoffset animation is universally supported across all modern browsers.

Techniques used in this demo

Search CodeFronts

Loading…