Build an SVG Progress Ring with stroke-dasharray
Create a progress ring with a normalized path length, a readable label, and a simple offset calculation.

A progress ring looks complicated until you think of it as a circle with part of its stroke hidden. With pathLength set to 100, the offset calculation is simple. This example draws a 65% ring and keeps the number visible alongside it.
Use a ring to support a number
A ring works best when the exact value is also available as text. Pick a meaningful measure, such as completed checklist items, rather than making an animation imply progress you have not measured. Keep the color understandable on the surrounding background and include a visible percentage.
Normalize the circle to 100 units
Setting pathLength to 100 makes the dash calculation convenient. A dash length of 100 covers the circle, and an offset of 35 leaves 65 units visible. Rotate the circle around its center so the progress begins at the top. This example represents a fixed 65 percent value.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="120" height="120" role="img" aria-label="65 percent complete">
<circle cx="50" cy="50" r="40" fill="none" stroke="#e4ecef" stroke-width="8"/>
<circle cx="50" cy="50" r="40" fill="none" stroke="#16839b" stroke-width="8" pathLength="100" stroke-dasharray="100" stroke-dashoffset="35" transform="rotate(-90 50 50)"/>
<text x="50" y="55" text-anchor="middle" font-size="18">65%</text>
</svg>Update the ring and label together
For a changing value, clamp the percentage between 0 and 100 and calculate the offset as 100 minus that value. Update the visible text and accessible description from the same number. If you animate between values, respect reduced-motion preferences and avoid constantly announcing every intermediate frame to assistive technology.
Keep reading

Make Holes in SVG Shapes with fill-rule
Understand evenodd fills with a small compound path and fix unexpectedly filled cutouts.
Read article