# Progress

> A bar that shows how much of a task is done, with a label, an optional value, tones, sizes, and a state for when no one knows yet.

- Name: `progress`
- Status: beta
- Categories: feedback
- File: the install writes `progress.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `class-variance-authority`. The install adds them.
- Other registry items: `@arbokui/dither`. The install adds them.
- Gallery: https://arbokui.com/components/progress
- Registry item: https://arbokui.com/r/progress.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/progress
   ```

## Use it for

- A task that takes more than a second or two and has a known end, like an upload or an import.
- Pass value={null} while the amount is not known yet. The bar pulses until you pass a number.
- Give it a label that says what is happening, like 'Uploading 3 photos'. Add showValue to show the percent.
- Use tone='success' or tone='danger' only to match what the task means, like a quota that is nearly full.

## Avoid it for

- A value that is not a task, like disk space used or a password's strength. That needs role="meter", which Progress does not set.
- Short waits under a second. Show nothing, or a spinner in the button that started it.
- Content that is still loading on the page. Use a skeleton in its place.
- Steps in a form. Use step dots or a list of steps.

## Props

- `value`: `number | null`. Required. How much is done, from min to max. null shows the pulsing state for an unknown amount.
- `label`: `ReactNode`. Visible text above the bar. Without it, TypeScript requires aria-label or aria-labelledby.
- `showValue`: `boolean`. Default `false`. Shows the value as text at the end of the label row. It shows nothing while value is null.
- `tone`: `"neutral" | "brand" | "danger" | "success" | "warning"`. Default `"neutral"`. Color of the filled part.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. Bar height: 4, 8, or 12px at the default density. lg also uses larger label text.
- `min`: `number`. Default `0`. The value at the empty end.
- `max`: `number`. Default `100`. The value at the full end. At max, the root gets data-complete.
- `format`: `Intl.NumberFormatOptions`. How the value reads, as text and to screen readers. The default is a percent of the range.
- `getAriaValueText`: `(formattedValue: string | null, value: number | null) => string`. Custom words for screen readers, like '3 of 10 files'.
- `texture`: `"flat" | "dither" | "gradient" | "grain"`. dither draws the filled part as dots, gradient fades it from faint at the start to full at the moving end, and grain speckles it. Hover fills gradient and grain in. A bar with no value keeps its stripes. Left out, the bar follows the Texture around it.
- `className`: `string`. Classes for the root. Later classes win, so className='max-w-xs' narrows it.

## Accessibility

- Renders role="progressbar" with aria-valuenow, aria-valuemin, aria-valuemax, and aria-valuetext.
- With value={null}, aria-valuenow is left out, which tells screen readers the amount is unknown.
- TypeScript requires a visible label, aria-label, or aria-labelledby, so the bar always has a name.
- In server HTML, only aria-label and aria-labelledby name the bar. The visible label is linked once JavaScript runs.
- Screen readers do not announce each change. Say when the task ends, for example with a message in an aria-live region.
- The fill uses the tone's color on the muted track. No contrast pair checks the fill, so the label and value carry the meaning.
- The fill slides with --motion-duration-slow. The unknown state pulses at --motion-pulse-duration, which reduced motion and motion off set to 0s. Its pale fill, not the solid one, still shows it is not done.

## Examples

### Upload with a label and percent

```tsx
<Progress value={40} label="Uploading 3 photos" showValue tone="brand" />
```

### Amount not known yet

```tsx
<Progress value={null} label="Preparing your export" />
```

### No visible label

```tsx
<Progress value={72} aria-label="Course progress" size="sm" />
```

### Counting items instead of percent

```tsx
<Progress
  value={3}
  max={10}
  label="Importing contacts"
  showValue
  format={{ style: "decimal" }}
/>
```

### Driven by state

```tsx
"use client";
import { useEffect, useState } from "react";

function Sync() {
  const [done, setDone] = useState(0);
  useEffect(() => {
    if (done >= 100) return;
    const timer = setTimeout(() => setDone((value) => Math.min(100, value + 10)), 400);
    return () => clearTimeout(timer);
  }, [done]);
  return <Progress value={done} label={done < 100 ? "Syncing" : "Synced"} showValue tone={done < 100 ? "brand" : "success"} />;
}
```

### A gradient bar, full at the moving end

```tsx
<Progress value={64} tone="brand" label="Uploading" showValue texture="gradient" />
```
