Skip to main content
ExodeUI

EXODEUI DESKTOP

A little space for good ideas.

Choose your platform to get the official ExodeUI installer.

Looking for Intel Mac or other builds? Explore the web studio or check all download options.

Prefer the browser? Try for free in your browser →
LanguageEnglish
Loading6 min read·Updated October 2026·By the ExodeUI team

How to Animate a Circular Progress Ring

How to build an animated progress ring with SVG, when to use determinate versus indeterminate progress, and why you should not animate fake progress.

Direct Summary & Definition

Core Takeaway

Use a determinate ring only when you actually know how far along the work is. Build it from an SVG circle, animate stroke-dashoffset, and update the value from real progress events. If progress is unknown, use an indeterminate indicator instead of a ring creeping toward 90 percent. Always pair the ring with a text or accessible value.

Determinate or indeterminate

A determinate ring shows a proportion. An indeterminate spinner shows that work is happening but not how much is left. They answer different questions, and mixing them is what produces the familiar fake progress bar that stalls at 90 percent. If you cannot measure progress, do not draw a ring that pretends to.

The SVG stroke technique

Draw a circle with fill none, give it a stroke, and set stroke-dasharray to the circumference. Then animate stroke-dashoffset from the full circumference down to the offset for the current percentage. Rotate the circle so the ring starts at twelve o'clock. Because this changes stroke properties rather than layout, it animates smoothly.

Ease the updates, not the truth

Progress events often arrive in jumps. Smoothing the visual transition between two real values is fine and looks better. Inventing movement between updates is not. Keep the displayed value tied to the last real measurement, and let it settle there.

Give slow work a visible state

If a step takes a while, a ring that barely moves looks frozen. Pair it with a label that describes the stage, and consider a subtle indeterminate motion inside the ring. Do not spin the whole determinate ring, which destroys the reading of progress.

Label it and expose the value

Put the percentage or step in text next to the ring, and give the element an accessible value with progressbar semantics. A purely visual ring is invisible to screen readers.

Performance

An SVG ring driven by stroke offset is usually cheaper than a canvas redraw or a layout-based bar. Animate it from a single value and avoid re-rendering a large component tree on every frame. Test on a mid-range phone, where loading screens are most common.

Production Implementation Snippet

Interactive Component Implementation
<svg viewBox="0 0 36 36" role="progressbar"
     aria-valuemin="0" aria-valuemax="100" aria-valuenow="65">
  <circle cx="18" cy="18" r="15.9" fill="none"
          stroke="#e5e5e5" stroke-width="3" />
  <circle cx="18" cy="18" r="15.9" fill="none"
          stroke="#ff4a1c" stroke-width="3"
          stroke-dasharray="100"
          stroke-dashoffset="35"
          transform="rotate(-90 18 18)" />
</svg>
<!-- pathLength="100" lets you use percentages directly. -->

Key Principles to Remember

  • Use a determinate ring only when progress is measurable
  • Animate stroke-dashoffset on an SVG circle
  • Smooth transitions between real values, never invent movement
  • Pair the ring with text and progressbar semantics
  • Test the loading screen on a mid-range phone

Frequently Asked Questions

Sources

  1. stroke-dasharray — MDN Web Docs
  2. ARIA progressbar role — MDN Web Docs
  3. Animation performance and frame rate — web.dev

Related Guides & Examples

Tune the loading state on a real device.

ExodeUI is a browser and desktop tool for interactive UI and animation. Build the ring's states visually, then test it in your app. Community is free.

Browse loading examples
Try for free