# Progress ring

A ring that fills clockwise from the top to show how far something has got, with the value in the middle.

Block: `smbk-progress-ring`. Status: experimental. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `size` | `md`, `sm` | `md` | Medium shows the value in the middle; small is a thinner ring with no room for it, for a row or a list. |
| `complete` | on / off | off | The value has reached the end: the whole ring turns to the success color. The wrapper sets it; plain markup sets it the same way. |

Parts: `smbk-progress-ring__track`, `smbk-progress-ring__value`

## Markup

```html
<div
  class="smbk-progress-ring smbk-progress-ring--size-md"
  role="progressbar"
  aria-valuemin="0"
  aria-valuemax="100"
  aria-valuenow="60"
  aria-label="Profile complete"
  style="--smbk-progress-ring-value:60"
>
  <span class="smbk-progress-ring__track" aria-hidden="true"></span>
  <span class="smbk-progress-ring__value" aria-hidden="true">60%</span>
</div>
```

## React

```tsx
import { ProgressRing } from './design-system/components/progress-ring/progress-ring.js';

<ProgressRing size="md" value={60} aria-label="Profile complete" />
```

## Stylesheet

```css
/* smbk-progress-ring — Progress ring. Figma "Progress ring" 7580:184 (Value × Size).
 * A ring in the subtle track color with the done part laid over it from the top, clockwise, in the
 * chart series color, its two ends round; at the end the whole ring turns to the success color. The
 * value in the middle in Label MD at the medium size; the small ring has no room for it.
 * The ring is one box: a conic gradient paints the done part over the track and a radial mask cuts
 * the hole, so its size and thickness come from tokens alone. The round ends are the box's two
 * pseudo-elements, dots as wide as the ring: one at the top, one turned round the center to where the
 * done part ends. The wrapper sets --smbk-progress-ring-value inline, from 0 to 100; that is data,
 * not a design value. */

@property --smbk-progress-ring-value {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

.smbk-progress-ring {
  box-sizing: border-box;
  display: inline-grid;
  flex-shrink: 0;
  place-items: center;
  width: var(--smbk-size-progress-ring-md);
  height: var(--smbk-size-progress-ring-md);
  vertical-align: middle;
}

.smbk-progress-ring__track {
  position: relative;
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  border-radius: var(--smbk-radius-full);
  background: conic-gradient(
    var(--smbk-color-data-series) calc(var(--smbk-progress-ring-value) * 1%),
    var(--smbk-color-surface-track-subtle) 0
  );
  mask-image: radial-gradient(
    farthest-side,
    transparent calc(100% - var(--smbk-size-6)),
    currentcolor calc(100% - var(--smbk-size-6))
  );
}

.smbk-progress-ring__track::before,
.smbk-progress-ring__track::after {
  content: "";
  position: absolute;
  top: 0;
  left: calc(50% - var(--smbk-size-6) / 2);
  width: var(--smbk-size-6);
  height: var(--smbk-size-6);
  border-radius: var(--smbk-radius-full);
  background: var(--smbk-color-data-series);
  /* Nothing is drawn at zero, as Figma draws it. */
  opacity: clamp(0, var(--smbk-progress-ring-value), 1);
}

.smbk-progress-ring__track::after {
  transform-origin: 50% calc(var(--smbk-size-progress-ring-md) / 2);
  transform: rotate(calc(var(--smbk-progress-ring-value) * 3.6deg));
}

.smbk-progress-ring__value {
  grid-area: 1 / 1;
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-label-md-font-family);
  font-size: var(--smbk-typography-label-md-font-size);
  font-weight: var(--smbk-typography-label-md-font-weight);
  line-height: var(--smbk-typography-label-md-line-height);
  letter-spacing: var(--smbk-typography-label-md-letter-spacing);
  text-transform: var(--smbk-typography-label-md-text-transform);
  font-variant-numeric: tabular-nums;
}

/* ---- complete: the whole ring in the success color ---- */

.smbk-progress-ring--complete .smbk-progress-ring__track {
  background: var(--smbk-color-feedback-success-icon);
}

.smbk-progress-ring--complete .smbk-progress-ring__track::before,
.smbk-progress-ring--complete .smbk-progress-ring__track::after {
  background: var(--smbk-color-feedback-success-icon);
}

/* ---- small: a smaller, thinner ring with no value in it ---- */

.smbk-progress-ring--size-sm {
  width: var(--smbk-size-progress-ring-sm);
  height: var(--smbk-size-progress-ring-sm);
}

.smbk-progress-ring--size-sm .smbk-progress-ring__track {
  mask-image: radial-gradient(
    farthest-side,
    transparent calc(100% - var(--smbk-size-4)),
    currentcolor calc(100% - var(--smbk-size-4))
  );
}

.smbk-progress-ring--size-sm .smbk-progress-ring__track::before,
.smbk-progress-ring--size-sm .smbk-progress-ring__track::after {
  left: calc(50% - var(--smbk-size-4) / 2);
  width: var(--smbk-size-4);
  height: var(--smbk-size-4);
}

.smbk-progress-ring--size-sm .smbk-progress-ring__track::after {
  transform-origin: 50% calc(var(--smbk-size-progress-ring-sm) / 2);
}

.smbk-progress-ring--size-sm .smbk-progress-ring__value {
  display: none;
}

/* ---- motion: when the value changes, the done part grows or shrinks to it with the state purpose,
   so the change can be followed. No movement under reduced motion. ---- */

.smbk-progress-ring {
  transition: --smbk-progress-ring-value var(--smbk-motion-state-duration) var(--smbk-motion-state-easing);
}

@media (prefers-reduced-motion: reduce) {
  .smbk-progress-ring {
    transition: none;
  }
}

/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills; this puts the meaning back with the user's own
   colors, as the progress bar does: the done part in Highlight over a track in the text color. Nothing
   here applies outside that mode. */

@media (forced-colors: active) {
  .smbk-progress-ring .smbk-progress-ring__track {
    forced-color-adjust: none;
    background: conic-gradient(Highlight calc(var(--smbk-progress-ring-value) * 1%), CanvasText 0);
  }

  .smbk-progress-ring .smbk-progress-ring__track::before,
  .smbk-progress-ring .smbk-progress-ring__track::after {
    forced-color-adjust: none;
    background: Highlight;
  }
}
```

## Tokens it reads

```json
{
  "size": {
    "4": {
      "$type": "dimension",
      "$value": "4px",
      "$description": "Raw size step, 4 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    },
    "6": {
      "$type": "dimension",
      "$value": "6px",
      "$description": "Raw size step, 6 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    },
    "progress-ring": {
      "md": {
        "$type": "dimension",
        "$value": "72px"
      },
      "sm": {
        "$type": "dimension",
        "$value": "36px"
      }
    }
  },
  "radius": {
    "full": {
      "$type": "dimension",
      "$value": "{{radius.9999}}",
      "$description": "Capsule: pills, chips, switches."
    }
  },
  "color": {
    "data": {
      "series": {
        "$type": "color",
        "$value": "{{palette.light.series}}",
        "$description": "The single series colour; the same as categorical 1, so one series and the first of many agree. Light blue/600, dark blue/400. Marks reach 3:1 on the plot background; never colour alone: direct labels, markers or patterns too."
      }
    },
    "surface": {
      "track-subtle": {
        "$type": "color",
        "$value": "{{color.grey.300}}",
        "$description": "The empty part of a progress track: light enough that the fill stands out by lightness, not only by hue."
      }
    },
    "text": {
      "default": {
        "$type": "color",
        "$value": "{{color.grey.900}}",
        "$description": "Main text."
      }
    },
    "feedback": {
      "success": {
        "icon": {
          "$type": "color",
          "$value": "{{color.green.600}}",
          "$description": "The success tone’s own icon (a check), so the tone is never shown by colour alone."
        }
      }
    }
  },
  "typography": {
    "label-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 500,
        "lineHeight": 1.4,
        "letterSpacing": "0px"
      },
      "$description": "Label MD: labels on controls and short UI text (buttons, chips, tabs, menu headings). Geist Medium, sentence case. Code and keys use Code SM and Code MD."
    }
  },
  "motion": {
    "state": {
      "duration": {
        "$type": "duration",
        "$value": "{{duration.200}}"
      },
      "easing": {
        "$type": "cubicBezier",
        "$value": "{{easing.cubic-in-out}}"
      }
    }
  }
}
```

## Purpose

Shows how far something has got as a ring that fills, with the percentage in the middle: a profile completed, a course taken, the steps of a setup done. It takes less room than a progress bar and sits well beside a name or at the start of a list row.

## Anatomy

- The block, a square as wide as the ring.
- The track part, the ring itself: the empty ring in the subtle track color, and the done part laid over it from the top, clockwise, in the chart color, with round ends.
- The value part, the percentage in the middle in a label style, at the medium size only.

## When to use

- For progress with a known end, beside the name of what is measured.
- In a list or a checklist, a small ring per row.

## When not to use

- For work whose end is not known. Use a [spinner](spinner.md) or a [progress bar](progress.md) with no value.
- For how full something is, such as storage. Use a [meter](meter.md).
- For a value over time or a share of several parts. Use a chart.

## Behavior

The value is kept inside the range. The done part grows clockwise from the top; at the end the whole ring turns to the success color. When the value changes, the done part grows or shrinks to it, and does not move under reduced motion. Nothing moves as the page loads.

The small ring is thinner and has no value in the middle: name it with words beside it.

## Accessibility

### What it does for you

- The ring is a progress bar to a screen reader, with its value, and is said as progress.
- The percentage in the middle is hidden from screen readers, which hear the value from the ring itself.
- It takes no focus and no keys.
- In high contrast mode the done part is drawn in the highlight color over a ring in the text color.

### What you must do

- Name it: an `aria-label`, or `aria-labelledby` pointing at the words beside it.
- When a percentage is not what it measures, give the value in words with `aria-valuetext`, such as "2 of 4 steps".
- Name the small ring with words beside it, since it has no value in the middle.

### How to test

- With a screen reader, it is read as a progress bar by its name, with its value, or with the words of `aria-valuetext` when given. The percentage in the middle is not read a second time.
- Change the value: the done part grows or shrinks to it, and with reduced motion it does not move.
- With text at 200% and the page 320 pixels wide, the ring and the words beside it stay whole.
- In a forced-color mode, the done part is drawn in the highlight color over a ring in the text color.

## Usage rules

### Do

- Say what the ring measures beside it.
- Use the small ring in rows, the medium one where it stands on its own.

### Don't

- Use a ring for work whose end is not known.
- Put a ring with no words near it where the reader cannot tell what it measures.
