Progress Ring

72%
Profile complete
45%
Storage used
100%
Course finished
18%
Budget spent
@use 'sprucecss/scss/spruce' as *;

.progress-ring-demo {
  @include layout-cluster('l', center);
}

.progress-ring {
  --value: 0;
  --size: 8rem;
  --stroke: 3;
  --color: #{color('primary')};
  align-items: center;
  display: grid;
  gap: spacer('xs');
  justify-items: center;
  margin: 0;

  svg {
    block-size: var(--size);
    grid-area: 1 / 1;
    inline-size: var(--size);
    rotate: -90deg;
  }

  circle {
    fill: none;
    stroke-width: var(--stroke);
  }

  &__track {
    stroke: color('progress-background', 'form');
  }

  &__bar {
    stroke: var(--color);
    stroke-dasharray: var(--value) 100;
    stroke-linecap: round;
    @include transition($property: 'stroke-dasharray');
  }

  &__value {
    color: color('heading');
    font-family: config('font-family-heading', $typography);
    font-size: calc(var(--size) / 4.5);
    font-weight: 700;
    grid-area: 1 / 1;
  }

  &__label {
    color: color('muted');
    font-size: config('font-size-sm', $typography);
    text-align: center;
  }

  &--sm {
    --size: 5rem;
    --stroke: 4;
  }

  &--success {
    --color: #{color('success', 'alert')};
  }

  &--warning {
    --color: #{color('warning', 'alert')};
  }
}

[dir='rtl'] .progress-ring svg {
  scale: -1 1;
}
<div class="progress-ring-demo">
    <figure class="progress-ring" style="--value: 72;">
        <svg viewBox="0 0 36 36" aria-hidden="true" focusable="false">
            <circle class="progress-ring__track" cx="18" cy="18" r="15.915"></circle>
            <circle class="progress-ring__bar" cx="18" cy="18" r="15.915" pathLength="100"></circle>
        </svg>
        <span class="progress-ring__value" role="img" aria-label="Profile complete: 72%">72%</span>
        <figcaption class="progress-ring__label">Profile complete</figcaption>
    </figure>
    <figure class="progress-ring progress-ring--warning" style="--value: 45;">
        <svg viewBox="0 0 36 36" aria-hidden="true" focusable="false">
            <circle class="progress-ring__track" cx="18" cy="18" r="15.915"></circle>
            <circle class="progress-ring__bar" cx="18" cy="18" r="15.915" pathLength="100"></circle>
        </svg>
        <span class="progress-ring__value" role="img" aria-label="Storage used: 45%">45%</span>
        <figcaption class="progress-ring__label">Storage used</figcaption>
    </figure>
    <figure class="progress-ring progress-ring--success" style="--value: 100;">
        <svg viewBox="0 0 36 36" aria-hidden="true" focusable="false">
            <circle class="progress-ring__track" cx="18" cy="18" r="15.915"></circle>
            <circle class="progress-ring__bar" cx="18" cy="18" r="15.915" pathLength="100"></circle>
        </svg>
        <span class="progress-ring__value" role="img" aria-label="Course finished: 100%">100%</span>
        <figcaption class="progress-ring__label">Course finished</figcaption>
    </figure>
    <figure class="progress-ring progress-ring--sm" style="--value: 18;">
        <svg viewBox="0 0 36 36" aria-hidden="true" focusable="false">
            <circle class="progress-ring__track" cx="18" cy="18" r="15.915"></circle>
            <circle class="progress-ring__bar" cx="18" cy="18" r="15.915" pathLength="100"></circle>
        </svg>
        <span class="progress-ring__value" role="img" aria-label="Budget spent: 18%">18%</span>
        <figcaption class="progress-ring__label">Budget spent</figcaption>
    </figure>
</div>

Progress Ring

On this page

A circular progress indicator for a percentage: profile completion, quotas or course progress.

Technical Details

  • One custom property drives it: style="--value: 72;". The circle uses pathLength="100", so the value is the dash length directly; no JavaScript or math needed.
  • The SVG is decorative; the number is exposed as an image with a full label ("Profile complete: 72%"), and the visible caption describes it. For a live, changing value, use a native progress element instead.
  • Sizes and colors are custom properties too (--size, --stroke, --color), with --sm, --success and --warning modifiers. The direction mirrors in RTL.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.