25 CSS Spinners23 / 25

Pure CSSMIT licensed

Progress Arc Fill Spinner

An SVG circle arc fills from empty to complete on a two-second loop, with a 100% percentage counter label centred inside — suitable as both a loading indicator and a determinate progress ring.

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="sp-23">
  <svg class="sp-23__svg" viewBox="0 0 90 90">
    <circle class="sp-23__track" cx="45" cy="45" r="40"/>
    <circle class="sp-23__arc" cx="45" cy="45" r="40"/>
    <text class="sp-23__percent" x="45" y="45">100%</text>
  </svg>
</div>
.sp-23,
.sp-23 *,
.sp-23 *::before,
.sp-23 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.sp-23 {
  --bg: #050a0f;
  --c: #00e5ff;
  --track: rgba(0,229,255,0.12);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: var(--bg);
}

.sp-23__svg {
  width: 90px;
  height: 90px;
  transform: rotate(-90deg);
  filter: drop-shadow(0 0 6px var(--c));
}

.sp-23__track {
  fill: none;
  stroke: var(--track);
  stroke-width: 5;
}

.sp-23__arc {
  fill: none;
  stroke: var(--c);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-dasharray: 251;
  stroke-dashoffset: 251;
  animation: sp-23-fill 2s ease-in-out infinite;
}

.sp-23__percent {
  fill: var(--c);
  font-size: 14px;
  font-family: system-ui,sans-serif;
  font-weight: 600;
  text-anchor: middle;
  dominant-baseline: central;
  transform: rotate(90deg);
  transform-origin: 45px 45px;
  animation: sp-23-count 2s ease-in-out infinite;
  letter-spacing: -0.5px;
}

@keyframes sp-23-fill {
  0% {
    stroke-dashoffset: 251;
    opacity: 1;
  }

  80% {
    stroke-dashoffset: 0;
    opacity: 1;
  }

  100% {
    stroke-dashoffset: 0;
    opacity: 0;
  }
}

@keyframes sp-23-count {
  0% {
    opacity: 1;
  }

  80% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sp-23__arc {
    animation: none;
    stroke-dashoffset: 100;
  }

  .sp-23__percent {
    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 Spinner 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: Progress Arc Fill Spinner
Source: https://codefronts.com/motion/css-spinner-designs/progress-arc-fill-spinner/

An SVG circle arc fills from empty to complete on a two-second loop, with a 100% percentage counter label centred inside — suitable as both a loading indicator and a determinate progress ring.
## HTML
```html
<div class="sp-23">
  <svg class="sp-23__svg" viewBox="0 0 90 90">
    <circle class="sp-23__track" cx="45" cy="45" r="40"/>
    <circle class="sp-23__arc" cx="45" cy="45" r="40"/>
    <text class="sp-23__percent" x="45" y="45">100%</text>
  </svg>
</div>
```
## CSS
```css
.sp-23,
.sp-23 *,
.sp-23 *::before,
.sp-23 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.sp-23 {
  --bg: #050a0f;
  --c: #00e5ff;
  --track: rgba(0,229,255,0.12);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: var(--bg);
}

.sp-23__svg {
  width: 90px;
  height: 90px;
  transform: rotate(-90deg);
  filter: drop-shadow(0 0 6px var(--c));
}

.sp-23__track {
  fill: none;
  stroke: var(--track);
  stroke-width: 5;
}

.sp-23__arc {
  fill: none;
  stroke: var(--c);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-dasharray: 251;
  stroke-dashoffset: 251;
  animation: sp-23-fill 2s ease-in-out infinite;
}

.sp-23__percent {
  fill: var(--c);
  font-size: 14px;
  font-family: system-ui,sans-serif;
  font-weight: 600;
  text-anchor: middle;
  dominant-baseline: central;
  transform: rotate(90deg);
  transform-origin: 45px 45px;
  animation: sp-23-count 2s ease-in-out infinite;
  letter-spacing: -0.5px;
}

@keyframes sp-23-fill {
  0% {
    stroke-dashoffset: 251;
    opacity: 1;
  }

  80% {
    stroke-dashoffset: 0;
    opacity: 1;
  }

  100% {
    stroke-dashoffset: 0;
    opacity: 0;
  }
}

@keyframes sp-23-count {
  0% {
    opacity: 1;
  }

  80% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sp-23__arc {
    animation: none;
    stroke-dashoffset: 100;
  }

  .sp-23__percent {
    animation: none;
  }
}
```

How this works

An SVG circle element uses stroke-dasharray:251 (the circumference of a 40px-radius circle: 2π×40 ≈ 251px) and starts at stroke-dashoffset:251 (fully hidden). The sp-23-fill keyframe animates the offset from 251 to 0 (fully filled arc) over 80% of the cycle, then briefly holds and fades — simulating a completion event before the next cycle begins.

The SVG is rotated -90deg in CSS so the arc starts at the 12 o'clock position rather than 3 o'clock (SVG's default start angle). The percentage text uses a counter-rotation transform so it reads upright despite the SVG's rotation. A faint track circle at the same radius provides the empty-arc guide.

Make it yours

  • Change stroke colour via --c — adapt to brand by matching primary action colour.
  • Set stroke-dashoffset to a specific value (e.g. 125 for 50%) using inline style or JS to display static progress instead of animating.
  • Add a second counter arc on the track circle using stroke-dashoffset animation in reverse to show remaining capacity.
  • Change stroke-linecap:round to square for sharp arc ends more suitable for geometric/technical UIs.
  • Increase the SVG viewBox and circle radius proportionally for a larger progress ring — recalculate the stroke-dasharray as 2 * Math.PI * radius.

Gotchas — read before shipping

  • The circumference value (251) is specific to r="40" — if you change the radius, recalculate as 2 * Math.PI * r and update stroke-dasharray accordingly.
  • The transform:rotate(-90deg) on the SVG element causes the percent text counter-rotation via transform-origin:45px 45px (the SVG's centre) — changing the SVG dimensions requires updating this origin.
  • stroke-dashoffset animation on an SVG circle is not composited (it triggers SVG repaint rather than GPU compositing) — acceptable for a single spinner but avoid dozens on one page.

Browser support

ChromeSafariFirefoxEdge
60+12+60+60+

SVG stroke animation universally supported; no modern CSS features required.

Techniques used in this demo

Search CodeFronts

Loading…