# Progress

> A React progress bar, stepped bar or ring for uploads and quotas, determinate or indeterminate, with a label, value, tones and thresholds.

- Type: React component (Feedback)
- Access: Free (the source is open to everyone)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add progress`
- Docs: https://wingo-ui.com/components/progress

## Interaction

It is read, not operated: it never takes focus. Every value change glides on a spring from where the bar was, with no re-render per frame; the first paint already shows the final value. Without a value the bar slides endlessly (the ring spins), and a value arriving later glides on from there. With thresholds a quota bar turns amber, then red, as it fills. Screen readers hear a progressbar named by the label, described by the description, with the value as a percent. Under reduced motion values jump, stripes stop and the indeterminate bar pulses in place. In the playground, press Simulate to run an import from 0 to 100% in uneven steps. The same markup works on a phone; the header wraps under 240px.

## Installation

```bash
npx wingo-ui@latest add progress
```

The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add https://wingo-ui.com/r/progress.json`

Files:

- `components/ui/progress.tsx`

npm dependencies: `npm i @radix-ui/react-progress motion`

Wingo UI dependencies (installed automatically):

- [useLocale](https://wingo-ui.com/components/use-locale) ([Markdown](https://wingo-ui.com/components/use-locale.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Number format](https://wingo-ui.com/components/number-format) ([Markdown](https://wingo-ui.com/components/number-format.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { Progress, ProgressRing } from "@/components/ui/progress"

export function ClientImport({ done, total }: { done: number; total: number }) {
  return (
    <div className="flex flex-col gap-4">
      <Progress
        label="Importing clients"
        description={`${done} of ${total} rows`}
        value={done}
        max={total}
        showValue
      />
      <Progress
        label="Storage"
        value={7.2}
        max={10}
        thresholds={{ warning: 80, danger: 95 }}
        showValue
        formatValue={(value, max) => `${value} GB of ${max} GB`}
      />
      <ProgressRing value={68} tone="brand" showValue aria-label="Monthly goal" />
    </div>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value?` | `number \| null` | `64` | The current value, from 0 to max. Omit it (or pass null, NaN) for the indeterminate state: a sliding bar or a spinning arc. Values outside 0..max are clamped for drawing. The playground starts at a sample 64; the component's default is null. |
| `max?` | `number` | `100` | The value that means done. 0 or less renders the indeterminate state. |
| `variant?` | `"linear" \| "segmented" \| "ring"` | `"linear"` | linear: one bar. segmented: equal steps (onboarding, password strength, stories); a fractional value fills a segment partly. ring: a circle for KPIs and compact rows. |
| `segments?` | `number` | `5` | Segmented only: how many equal steps. 2,5 of 5 shows two full segments and one half. |
| `size?` | `"xs" \| "sm" \| "md" \| "lg"` | `"md"` | Linear track 2 / 4 / 6 / 8px (xs is the hairline for a refreshing list); ring 24 / 32 / 48 / 64px with a 3 / 3 / 4 / 6px stroke. The xs ring shows its value beside it. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The fill color. The track stays a neutral tint. A finished upload does not turn green on its own: pass success when it should. |
| `color?` | `string` | `""` | Any CSS color for the fill; replaces the tone. Empty uses the tone. |
| `radius?` | `"none" \| "sm" \| "full"` | `"full"` | Ends of the linear and segmented bars: square, 2px or a pill. |
| `label?` | `ReactNode` | `""` | Above the bar, or beside the ring with labelPosition side. Names the progressbar for screen readers (else pass aria-label). |
| `description?` | `ReactNode` | `""` | Under the bar in small muted text, e.g. "2.4 MB of 12 MB". Linked with aria-describedby. |
| `showValue?` | `boolean` | `false` | Shows the formatted value at the end of the header row (linear) or in the center of the ring. |
| `valueFormat?` | `"percent" \| "fraction" \| "value"` | `"percent"` | How the value reads: "64%", "32 / 50" or "32", in the locale ("64 %" in de-DE). |
| `labelPosition?` | `"top" \| "side"` | `"top"` | Ring only: top stacks label, ring and description; side puts the text to the right of the ring (KPI rows). |
| `striped?` | `boolean` | `false` | Linear only: moving diagonal stripes on the fill while it is below max. They stop under reduced motion. |
| `animated?` | `boolean` | `true` | Value changes glide on a spring. Off, the bar jumps straight to the new value. |
| `locale?` | `string` | `""` | Intl locale for the value. Empty uses the UIProvider locale, then the page language. |
| `thresholds?` | `{ warning?: number; danger?: number }` |  | Quotas: at or above warning % the fill turns warning, at danger % danger. Compares percentages, not raw values. Wins over tone and color. |
| `buffer?` | `number` | `0` | Linear only: a lighter second bar behind the fill, in the same units as value (media buffering, uploaded vs processed). Glides on the same spring. Omitted (0 in the playground) draws none. |
| `thickness?` | `number` | `0` | Ring only: the stroke width in px. 0 uses 3 / 3 / 4 / 6px by size. |
| `formatValue?` | `(value: number, max: number) => ReactNode` |  | Replaces valueFormat, e.g. "7.2 GB of 10 GB". |
| `getValueLabel?` | `(value: number, max: number) => string` |  | The aria-valuetext. Defaults to the percent in the locale. |
| `children?` | `ReactNode` |  | Ring only: replaces the centered value (an icon, a figure in your own style). |
| `classNames?` | `Partial<Record<"header" \| "label" \| "value" \| "track" \| "indicator" \| "segment" \| "ring" \| "description", string>>` |  | Extra classes for the inner parts. |
| `className?` | `string` |  | Extra classes for the root, merged last. Other props (aria-label, aria-labelledby, aria-describedby) reach the progressbar. |

## Parts

### ProgressRing (component)

The bare ring for other components (a stat card, an avatar, a file row). Same value props as Progress, children in the center (centered text shrinks to fit) and its own global defaults under progress-ring. Pass it an aria-label.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value?` | `number \| null` | `"null"` | The current value; null spins the ring as indeterminate. |
| `max?` | `number` | `100` | The value that fills the ring. |
| `size?` | `"xs" \| "sm" \| "md" \| "lg" \| number` | `"md"` | 24 / 32 / 48 / 64px, or any px number. |
| `thickness?` | `number` |  | Stroke width in px; 3 / 3 / 4 / 6px by size when left out. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The ring color from the design tokens. |
| `color?` | `string` |  | Any CSS color; replaces the tone. |
| `showValue?` | `boolean` | `false` | Prints the formatted value in the center. |
| `valueFormat?` | `"percent" \| "fraction" \| "value"` | `"percent"` | How the centered value reads: "68%", "34 / 50" or "34". |
| `animated?` | `boolean` | `true` | Value changes glide on a spring; false jumps. |
| `classNames?` | `Partial<Record<"ring" \| "content", string>>` |  | Extra classes for the svg ring and the centered content. |

## Examples

- **Upload rows**: Small bars with the size in MB, a finished file in success and one still processing.
- **Storage quota**: Thresholds at 80% and 95% turn the bar amber, then red. Drag the slider.
- **Rings as KPIs**: labelPosition side puts the label and the description next to the ring.
- **Segmented onboarding**: Four steps with a fraction value; each step glides into the next. Try Back and Continue.
- **Indeterminate**: No value: a sliding bar, a spinning arc and an xs hairline on top of a refreshing card.
- **Sizes, tones, stripes**: xs to lg, every tone, a custom color, stripes with a buffer and rings with an icon.

## See also

- [Spinner](https://wingo-ui.com/components/spinner) ([Markdown](https://wingo-ui.com/components/spinner.md))
- [Route Progress](https://wingo-ui.com/components/route-progress) ([Markdown](https://wingo-ui.com/components/route-progress.md))
- [Scroll Progress](https://wingo-ui.com/components/scroll-progress) ([Markdown](https://wingo-ui.com/components/scroll-progress.md))
- [File Upload](https://wingo-ui.com/components/file-upload) ([Markdown](https://wingo-ui.com/components/file-upload.md))
- [Steps](https://wingo-ui.com/components/steps) ([Markdown](https://wingo-ui.com/components/steps.md))
- [Skeleton](https://wingo-ui.com/components/skeleton) ([Markdown](https://wingo-ui.com/components/skeleton.md))

---

Canonical: https://wingo-ui.com/components/progress
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
