# Progress

Show how much of an operation has completed.

Upload progress bar

[Complete TSX file](/examples/progress/default.tsx)

```tsx
"use client";

import { useEffect, useState } from "react";
import { Progress } from "@/siteplane/components/progress";

export default function Example() {
  const [value, setValue] = useState(0);

  // Simulates an upload: new values arrive in uneven steps, the upload rests at
  // 100 and starts again. The indicator moves evenly toward each value.
  useEffect(() => {
    const timer = window.setTimeout(
      () =>
        setValue(
          value >= 100
            ? 0
            : Math.min(100, value + 3 + Math.floor(Math.random() * 7)),
        ),
      value >= 100 ? 1600 : 400,
    );
    return () => window.clearTimeout(timer);
  }, [value]);

  return (
    <Progress aria-label="Upload progress" className="max-w-sm" value={value} />
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/progress
```

With npm: `npx shadcn@4.18.0 add @siteplane/progress`. [Registry JSON](/registry/progress.json) lists source files and dependencies.

## Usage

```tsx
import { Progress } from "@/siteplane/components/progress";

<Progress value={40} aria-label="Upload progress" />;
```

## Composition

```text
Progress
├── ProgressLabel
├── ProgressValue
└── ProgressTrack
    └── ProgressIndicator
```

## Label and value

Supplying children replaces the automatic track. Include Track and Indicator explicitly.

Progress with label and value

[Complete TSX file](/examples/progress/label.tsx)

```tsx
"use client";

import { useEffect, useState } from "react";
import {
  Progress,
  ProgressIndicator,
  ProgressLabel,
  ProgressTrack,
  ProgressValue,
} from "@/siteplane/components/progress";

export default function Example() {
  const [value, setValue] = useState(0);

  // Simulates an upload: new values arrive in uneven steps, the upload rests at
  // 100 and starts again.
  useEffect(() => {
    const timer = window.setTimeout(
      () =>
        setValue(
          value >= 100
            ? 0
            : Math.min(100, value + 3 + Math.floor(Math.random() * 7)),
        ),
      value >= 100 ? 1600 : 400,
    );
    return () => window.clearTimeout(timer);
  }, [value]);

  return (
    <Progress className="max-w-sm" value={value}>
      <div className="flex justify-between gap-4">
        <ProgressLabel>Upload progress</ProgressLabel>
        <ProgressValue />
      </div>
      <ProgressTrack>
        <ProgressIndicator />
      </ProgressTrack>
    </Progress>
  );
}
```

## Range and formatting

Values and formatting belong to the root. This example counts megabytes instead of percentages and adds the total to the displayed value.

Upload size in megabytes

[Complete TSX file](/examples/progress/range.tsx)

```tsx
"use client";

import { useEffect, useState } from "react";
import {
  Progress,
  ProgressIndicator,
  ProgressLabel,
  ProgressTrack,
  ProgressValue,
} from "@/siteplane/components/progress";

export default function Example() {
  const [value, setValue] = useState(0);

  // Simulates a backup upload that reports megabytes: it rests at 500 MB and
  // starts again.
  useEffect(() => {
    const timer = window.setTimeout(
      () =>
        setValue(
          value >= 500
            ? 0
            : Math.min(500, value + 12 + Math.floor(Math.random() * 28)),
        ),
      value >= 500 ? 1600 : 400,
    );
    return () => window.clearTimeout(timer);
  }, [value]);

  return (
    <Progress
      className="max-w-sm"
      value={value}
      max={500}
      format={{ style: "unit", unit: "megabyte" }}
    >
      <div className="flex justify-between gap-4">
        <ProgressLabel>Uploading backup</ProgressLabel>
        <ProgressValue>
          {(formattedValue) => `${formattedValue} of 500 MB`}
        </ProgressValue>
      </div>
      <ProgressTrack>
        <ProgressIndicator />
      </ProgressTrack>
    </Progress>
  );
}
```

## Circular progress

ProgressCircle is a separate, self-contained component; it takes a value and no children.

Circular progress sizes

[Complete TSX file](/examples/progress/circle.tsx)

```tsx
"use client";

import { useEffect, useState } from "react";
import { ProgressCircle } from "@/siteplane/components/progress";

export default function Example() {
  const [value, setValue] = useState(0);

  // Simulates an upload: new values arrive in uneven steps, the upload rests at
  // 100 and starts again. Every circle follows the same value.
  useEffect(() => {
    const timer = window.setTimeout(
      () =>
        setValue(
          value >= 100
            ? 0
            : Math.min(100, value + 3 + Math.floor(Math.random() * 7)),
        ),
      value >= 100 ? 1600 : 400,
    );
    return () => window.clearTimeout(timer);
  }, [value]);

  return (
    <div className="flex items-center gap-4">
      {(["sm", "md", "lg", "xl"] as const).map((size) => (
        <ProgressCircle
          key={size}
          aria-label="Upload progress"
          size={size}
          value={value}
        />
      ))}
    </div>
  );
}
```

## Smooth value changes

The indicator moves evenly toward each new value using the existing `--motion-duration-linger` token (380 ms by default). Incoming updates can continue an active transition. Numeric and accessible progress values reflect the actual value immediately.

The circle uses `--motion-duration-slow`. OS and kit reduced-motion settings disable these transitions, so the bar and the circle then jump to each new value. The application still owns the real progress; the component does not simulate uploads or generate additional progress values. The examples on this page stand in for a real upload with a timer: a new value every few hundred milliseconds, a rest at the end and a fresh start.

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| Progress.size | sm \| md \| lg \| xl | provider / lg | Root size inherited by Label, Track and Value. |
| Progress.locale | string | en-US | Explicit kit formatting default; not automatically the locale provider. |
| Progress.children | ReactNode | Track with Indicator | Custom children replace the automatic track. |
| Label / Track / Value.size | sm \| md \| lg \| xl | root / provider / lg | Override the part size. |

`ProgressLabel` associates the visible label; `ProgressTrack` contains `ProgressIndicator`; `ProgressValue` formats the current root value; a function child receives the formatted and the raw value. Indicator has no owned props. All styled parts accept string className. Root receives value, min, max, format and accessibility labels from [Base UI Progress](https://base-ui.com/react/components/progress); those numerical and formatting contracts are inherited, not separate Siteplane UI APIs. Pass `value={null}` for an indeterminate bar when completion is unknown; a fill then runs from the start of the track again and again. With reduced motion the bar and the circle hold still, as a segment in the middle of the track and an arc on top of the circle.

`ProgressProps`, `ProgressLabelProps`, `ProgressTrackProps` and `ProgressValueProps` export these same size extensions. `ProgressPrimitive` is the foundation namespace.

### ProgressCircle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| value | number \| null | required | Numeric completion; non-finite values use min. null shows an indeterminate circle. |
| min / max | number | 0 / 100 | Range; visual percentage clamps to 0–100 and is zero for a non-positive range. |
| size | sm \| md \| lg \| xl | provider / lg | Circle sizes 20, 28, 32 and 40 CSS pixels respectively. |
| locale | string | en-US | Formatting locale. |

`ProgressCircleProps` omits children, size and value from the foundation Root props and defines its own size and value. With `value={null}` an arc travels around the circle while completion is unknown.

[Read the installed source](/source/progress).

## Usage notes

The application owns timing, cancellation and failures. Keep the accessible label stable as the value changes; use a separate message for a failure or completion that needs an announcement.
