Skip to content
Knsept
Free playbook

Progress ring

A circular progress indicator from one SVG circle and stroke-dasharray, with an accessible progressbar role.

CSSJavaScriptUI Widgets Updated Oct 23, 2026
Live demo Reduced motion is on, so effects are toned down
<div class="ring-demo">
  <div class="ring" role="progressbar" aria-label="Upload progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="40" style="--progress: 40">
    <svg viewBox="0 0 120 120" aria-hidden="true">
      <circle class="ring-track" cx="60" cy="60" r="52" pathLength="100"/>
      <circle class="ring-fill" cx="60" cy="60" r="52" pathLength="100"/>
    </svg>
    <span class="ring-label">40%</span>
  </div>
  <div class="ring-buttons">
    <button type="button" data-change="-10">− 10%</button>
    <button type="button" data-change="10">+ 10%</button>
  </div>
</div>

The ring is two circles: a faint track and a colored fill. Setting pathLength="100" tells the browser to treat each circle's length as exactly 100 units, so the dash pattern is plain percentages: stroke-dasharray: 40 100 draws 40% of the circle and leaves the rest empty. Rotating the SVG by −90° makes it start at 12 o'clock.

Because the fill is a CSS transition on stroke-dasharray, every change animates smoothly with no JavaScript animation code. role="progressbar" with aria-valuenow lets screen readers announce the value.

Use it for real: call setProgress() from your upload or task progress events.

More from the library

Feedback live

Loading-state button

A submit button that shows a spinner while it works, then success or an error, without changing width.

Feedback live

Confetti burst

A celebration burst from a button, drawn on a canvas with simple physics. Under 60 lines, no library.

Feedback live

Skeleton loader

Shimmering placeholders that hold the layout while content loads, so nothing jumps when it arrives.