CircleProgress

A circular progress indicator that fills clockwise and swaps to a checkmark once the task is complete.

CircleProgress is a compact ring that visualises value out of total as a clockwise arc. When value >= total the arc fades out and a checkmark fades in on top of a filled ring. Both transitions are 200–300ms, so the component is at home inline with status text.

Reach for CircleProgress when the reader benefits from seeing a proportion at a glance: three of five onboarding steps done, forty of a hundred records imported. For an open-ended wait where no percentage is available, use <Loading> instead.

Import

import { CircleProgress } from "@unkey/ui";

Progress

The arc grows clockwise from twelve o’clock as value approaches total. Any ratio above 1 is clamped to 100%, at which point the checkmark state takes over.

Variants

variant recolors the progress arc, the completed ring, and the checkmark together. primary is the neutral default; success, warning, and error signal semantic intent; secondary sits quietly next to body copy.

Size

iconSize uses the shared size-weight system from @unkey/icons, so the ring aligns with neighboring icons and text. Size picks the outer diameter (sm, md, lg, xl, 2xl); weight picks the stroke thickness (thin, medium, regular, bold).

Stroke weight scales independently of diameter. Pair a larger ring with a thinner stroke when the component sits in a hero empty state, or a smaller ring with a bolder stroke to keep it legible inside a table row.

Completion

When value reaches or exceeds total, the arc fades out and a filled ring with a checkmark fades in. The swap happens automatically — you don’t need to conditionally render a separate “done” state.

Accessibility

CircleProgress renders a decorative <svg> inside a plain <div> with no built-in ARIA semantics. When the ring represents a real in-flight operation, add the semantics on the wrapping element yourself:

  • Set role="progressbar" and the matching aria-valuenow, aria-valuemin, and aria-valuemax on the wrapping <div> (via the pass-through props), or on a sibling container.
  • Add an aria-label or aria-labelledby that names what is progressing: "Onboarding steps", "Import progress". A generic “progress” is rarely enough.
  • Once value >= total, announce completion through the surrounding region (for example with an aria-live="polite" status message). The checkmark swap is purely visual.

If the ring is decorative (say, a placeholder glyph), leave it unlabeled and wrap it in a container with aria-hidden="true".

Props

value number Required

Current progress, expressed in the same units as total. Must be non-negative and finite; the component throws if it isn’t.

total number Required

The value that represents 100% completion. Must be greater than zero and finite; the component throws otherwise. Ratios above 1 are clamped.

variant "primary" | "secondary" | "success" | "warning" | "error" Optional Default "primary"

Semantic color applied to the progress arc, the completed ring, and the checkmark. Pick the intent that matches the state, not the hue.

iconSize "sm-thin" | "sm-medium" | ... | "2xl-bold" Optional Default "md-regular"

Size token from the shared @unkey/icons sizing system. The first half (sm through 2xl) picks the outer diameter (12px through 30px); the second half (thin, medium, regular, bold) picks the stroke thickness.

className string Optional

Additional Tailwind classes. Merged with the variant classes via cn.

...rest HTMLAttributes<HTMLDivElement> Optional

All standard <div> attributes pass through to the wrapping element, so this is where you attach role="progressbar", aria-valuenow, and friends.

  • Loading — for open-ended waits where you can’t express a percentage.
  • Button<Button loading> embeds its own spinner when the action is tied to a button press.