# Progress

An accessible React progress bar and ring for Next.js and Tailwind CSS that show how far along a task is, with an indeterminate state.

Source: https://ui.fewtone.com/components/progress

## Installation

```bash
npx shadcn@latest add https://ui.fewtone.com/r/progress.json
```

## Usage

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

export function Upload({ percent }: { percent: number }) {
  return <Progress value={percent} aria-label="Upload" />
}
```

## API reference

### Progress

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number \| null` | – | How far along, from 0 to max. Leave it out, or pass null, while there's no telling: a segment sweeps across instead. |
| `max` | `number` | `100` | The value that means done. |
| `getValueLabel` | `(value: number, max: number) => string` | `"64%"` | What screen readers say for the value. |

### ProgressRing

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number \| null` | – | How far along, from 0 to max. Without it, an arc turns round the ring. |
| `max` | `number` | `100` | The value that means done. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 20, 40 or 64px. Small fits beside text like an icon. |
| `showValue` | `boolean` | `false` | Shows the percentage in the middle, counting as it changes. Medium and large only. |

### ProgressValue

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number \| null` | – | The same value as the Progress it labels. |
| `max` | `number` | `100` | The same max. |

**When to use it:** Use Progress (a bar) or ProgressRing to show how far along a task is, such as an upload or a multi-step setup; leave out value while the length is unknown. Use Spinner for a small busy indicator with no amount, and LoadingButton for a button that waits on its own action.
**Built on:** Radix Progress.
**npm packages (installed by the CLI):** class-variance-authority@^0.7.1, motion@^14.0.0, radix-ui@^1.7.0.

## Examples

### Bar and ring

Press Run to step the value: the bar's fill, the ring's arc and the number rise continuously, each change lasting until the next one comes, so steady updates read as one smooth rise. Without a value both are indeterminate; with reduced motion they pulse instead.

```tsx
"use client";

import { Button } from "@/components/ui/button";
import { Progress, ProgressRing, ProgressValue } from "@/components/ui/progress";
import { PlayIcon } from "lucide-react";
import { useEffect, useRef, useState } from "react";

/** Pretend upload: where it's got to at each step */
const steps = [0, 12, 27, 41, 58, 72, 86, 100];

/*
 * A bar and a ring at the same value, and both while indeterminate, named underneath (each row lines its
 * names up at the bottom). Run steps the value from 0 to 100 every 0.7s, and the fill, the arc and the number
 * rise continuously through the steps rather than in parts.
 */
export default function ProgressOverview() {
  const [value, setValue] = useState(64);
  const timer = useRef<ReturnType<typeof setInterval> | null>(null);

  const stop = () => {
    if (timer.current) clearInterval(timer.current);
    timer.current = null;
  };
  // Leaving the page mid-run stops the steps
  useEffect(
    () => () => {
      if (timer.current) clearInterval(timer.current);
    },
    [],
  );

  const run = () => {
    stop();
    let step = 0;
    setValue(steps[0] ?? 0);
    timer.current = setInterval(() => {
      step += 1;
      const next = steps[step];
      if (next === undefined) stop();
      else setValue(next);
    }, 700);
  };

  return (
    <div className="flex w-full flex-col gap-6 self-stretch">
      <div className="-mt-6 -mr-6 flex items-center justify-end">
        <Button variant="outline" size="sm" onClick={run}>
          <PlayIcon aria-hidden />
          Run
        </Button>
      </div>

      <ul className="mx-auto grid w-full max-w-md flex-1 grid-cols-2 content-center items-end justify-items-center gap-x-10 gap-y-8">
        <li className="flex w-full flex-col items-center gap-2">
          <div className="flex w-full items-center gap-3">
            <Progress value={value} aria-label="Upload" />
            <ProgressValue value={value} className="w-10 justify-end text-xs text-muted-foreground" />
          </div>
          <span className="text-xs text-muted-foreground">Bar</span>
        </li>
        <li className="flex flex-col items-center gap-2">
          <ProgressRing value={value} size="lg" showValue aria-label="Upload" />
          <span className="text-xs text-muted-foreground">Ring</span>
        </li>
        <li className="flex w-full flex-col items-center gap-2">
          <div className="flex h-4 w-full items-center">
            <Progress aria-label="Loading" />
          </div>
          <span className="text-xs text-muted-foreground">Indeterminate</span>
        </li>
        <li className="flex flex-col items-center gap-2">
          <ProgressRing size="lg" aria-label="Loading" />
          <span className="text-xs text-muted-foreground">Indeterminate</span>
        </li>
      </ul>

      <div aria-hidden className="-mb-6 h-(--size-control-sm)" />
    </div>
  );
}
```

### With a label

Name the bar with a visible label (aria-labelledby) and describe it with a hint. ProgressValue shows the percentage beside the label.

```tsx
import { Progress, ProgressValue } from "@/components/ui/progress";
import { useId } from "react";

/*
 * A visible label names the bar (aria-labelledby), and a hint under it says what the number means. The
 * percentage beside the label is for sighted readers; the bar already tells screen readers its value.
 */
export default function ProgressWithLabel() {
  const id = useId();
  const value = 45;

  return (
    <div className="grid w-full max-w-xs gap-2">
      <div className="flex items-center justify-between gap-4 text-sm">
        <span id={`${id}-label`} className="font-medium">
          Uploading report.pdf
        </span>
        <ProgressValue value={value} className="text-muted-foreground" />
      </div>
      <Progress value={value} aria-labelledby={`${id}-label`} aria-describedby={`${id}-hint`} />
      <p id={`${id}-hint`} className="text-xs text-muted-foreground">
        1.7 MB of 3.8 MB
      </p>
    </div>
  );
}
```

### Ring sizes

Small sits beside text like an icon; medium and large have room for the value.

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

/* The ring's three sizes, named underneath: small sits beside text like an icon; medium and large fit the value */
export default function ProgressRingSizes() {
  return (
    <ul className="flex flex-wrap items-end justify-center gap-x-10 gap-y-6">
      <li className="flex flex-col items-center gap-2">
        <span className="flex items-center gap-2 text-sm">
          <ProgressRing value={30} size="sm" aria-label="Syncing" />
          Syncing
        </span>
        <span className="text-xs text-muted-foreground">Small</span>
      </li>
      <li className="flex flex-col items-center gap-2">
        <ProgressRing value={60} showValue aria-label="Storage used" />
        <span className="text-xs text-muted-foreground">Medium</span>
      </li>
      <li className="flex flex-col items-center gap-2">
        <ProgressRing value={90} size="lg" showValue aria-label="Course completed" />
        <span className="text-xs text-muted-foreground">Large</span>
      </li>
    </ul>
  );
}
```

## Related

- [Spinner](https://ui.fewtone.com/components/spinner.md): An accessible React spinner for Next.js and Tailwind CSS: one line going round an infinity that fills it once the task is done.
- [Loading Button](https://ui.fewtone.com/components/loading-button.md): An accessible React loading button for Next.js that shows loading, success and error states for async actions, without changing width.
- [Toast](https://ui.fewtone.com/components/toast.md): An accessible React toast for Next.js: notifications that slide in, stack like a deck and swipe away, with a toast() function and Radix Toast parts.
