# Shadcn Progress

> Free shadcn/ui progress bar for React — built on Radix. A determinate bar with a value, plus labels, colors, sizes, an indeterminate loading state, an upload simulation, and a circular variant.

Source: https://designrevision.com/components/progress

The **Shadcn Progress** is a **Tailwind progress bar** built on <a href="https://www.radix-ui.com/primitives/docs/components/progress" rel="nofollow">Radix</a>: a rounded track with an indicator that fills to a `value` and animates smoothly between updates. The examples below cover the determinate bar, labels, colors, sizes, an indeterminate loading state, a live upload, and a circular variant.

## A value-driven bar

The whole API is one prop — `value`, from 0 to 100:

```tsx
<Progress value={66} />
```

The indicator fills via a CSS `transform` with a `transition`, so when the value changes the bar animates to the new position. Drive it from state to make it move:

```tsx
const [value, setValue] = React.useState(0)
React.useEffect(() => {
  const t = setTimeout(() => setValue(66), 400)
  return () => clearTimeout(t)
}, [])
```

## A label and percentage

Pair the bar with a label row for the storage/usage pattern:

```tsx
<div className="flex items-center justify-between text-sm">
  <span className="text-muted-foreground">Storage used</span>
  <span className="font-medium">{value}%</span>
</div>
<Progress value={value} />
```

## Color and size

It's a Tailwind component, so restyle it with a `className`:

- **Color** — recolor the indicator with a child selector: `className="[&>[data-slot=progress-indicator]]:bg-green-500"`. The Colors example shows success / warning / destructive.
- **Size** — override the track height: `h-1`, `h-2` (default), or `h-4`.

## Indeterminate

When you can't measure progress, show a **sliding** bar instead of a filled value — an absolutely-positioned indicator that animates across the track with a small keyframe:

```css
@keyframes progress-indeterminate {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(400%); }
}
```

The Indeterminate example includes the markup and the keyframe.

## Live progress

Wire the value to real progress — an upload's `onprogress`, a multi-step flow, or a timer. The Upload simulation example climbs from 0 to 100% on an interval to show the bar moving.

## Circular progress

A ring is a different shape: draw two SVG circles and animate `stroke-dashoffset` from the full circumference down to the filled fraction. The Circular progress example does this with a centered percentage — handy for compact dashboards.

## Accessibility

Radix gives the bar the `progressbar` role and the right `aria-valuenow` / `aria-valuemax`, so assistive tech reads the percentage. For an indeterminate bar, omit the value so it is announced as "loading" rather than a specific number.

## Installation

### progress

**Install:**

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

**Dependencies:** @radix-ui/react-progress, utils

**Props**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| value | number | 0 | The progress percentage (0–100). The indicator fills to this width. |
| className | string | — | Style the track — height (h-1 / h-2 / h-4), color, radius. |

**Usage & accessibility:** A Radix progress bar — a track (bg-primary/20, h-2 rounded-full) with an indicator that fills to value via a translateX transform, so changes animate smoothly. Pass value 0–100; omit it (or leave the value undefined) and style a sliding indicator for an indeterminate bar. Restyle height/color with a className.

```tsx
// components/ui/progress.tsx
"use client"

import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"

import { cn } from "@/lib/utils"

function Progress({
  className,
  value,
  ...props
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
  return (
    <ProgressPrimitive.Root
      data-slot="progress"
      className={cn(
        "bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
        className
      )}
      {...props}
    >
      <ProgressPrimitive.Indicator
        data-slot="progress-indicator"
        className="bg-primary h-full w-full flex-1 transition-all"
        style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
      />
    </ProgressPrimitive.Root>
  )
}

export { Progress }
```

## Examples

### Determinate

The canonical progress bar — animates from 0 to its `value` on mount.

**Install:**

```bash
npx shadcn@latest add @designrevision/progress-demo-01
```

**Dependencies:** progress

```tsx
// components/ui/progress-demo-01.tsx
"use client"

import * as React from "react"

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

export default function ProgressDemo01() {
  const [value, setValue] = React.useState(0)

  React.useEffect(() => {
    const timer = setTimeout(() => setValue(66), 400)
    return () => clearTimeout(timer)
  }, [])

  return (
    <div className="w-full max-w-sm">
      <Progress value={value} />
    </div>
  )
}
```

---

### With label

A progress bar with a label and percentage readout — the storage/usage pattern.

**Install:**

```bash
npx shadcn@latest add @designrevision/progress-label-01
```

**Dependencies:** progress

```tsx
// components/ui/progress-label-01.tsx
import { Progress } from "@/components/ui/progress"

export default function ProgressLabel01() {
  const value = 72

  return (
    <div className="w-full max-w-sm space-y-2">
      <div className="flex items-center justify-between text-sm">
        <span className="text-muted-foreground">Storage used</span>
        <span className="font-medium">{value}%</span>
      </div>
      <Progress value={value} />
    </div>
  )
}
```

---

### Colors

Recolor the indicator with a child selector — success, warning, destructive.

**Install:**

```bash
npx shadcn@latest add @designrevision/progress-colors-01
```

**Dependencies:** progress

```tsx
// components/ui/progress-colors-01.tsx
import { Progress } from "@/components/ui/progress"

export default function ProgressColors01() {
  return (
    <div className="w-full max-w-sm space-y-4">
      <Progress
        value={80}
        className="[&>[data-slot=progress-indicator]]:bg-green-500"
      />
      <Progress
        value={55}
        className="[&>[data-slot=progress-indicator]]:bg-amber-500"
      />
      <Progress
        value={30}
        className="[&>[data-slot=progress-indicator]]:bg-destructive"
      />
    </div>
  )
}
```

---

### Sizes

Three track heights — override with `h-1` / `h-2` / `h-4`.

**Install:**

```bash
npx shadcn@latest add @designrevision/progress-sizes-01
```

**Dependencies:** progress

```tsx
// components/ui/progress-sizes-01.tsx
import { Progress } from "@/components/ui/progress"

export default function ProgressSizes01() {
  return (
    <div className="w-full max-w-sm space-y-4">
      <Progress value={60} className="h-1" />
      <Progress value={60} className="h-2" />
      <Progress value={60} className="h-4" />
    </div>
  )
}
```

---

### Indeterminate

A sliding loading bar for unknown duration, via a small keyframe.

**Install:**

```bash
npx shadcn@latest add @designrevision/progress-indeterminate-01
```

**Dependencies:** progress

```tsx
// components/ui/progress-indeterminate-01.tsx
// An indeterminate progress bar — for when you can't measure percent complete.
// The sliding indicator uses the .animate-progress-indeterminate keyframe from globals.css.
export default function ProgressIndeterminate01() {
  return (
    <div className="w-full max-w-sm">
      <div className="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
        <div className="animate-progress-indeterminate bg-primary absolute inset-y-0 left-0 w-1/3 rounded-full" />
      </div>
    </div>
  )
}
```

---

### Upload simulation

A live bar driven by state — climbs from 0 to 100% on an interval.

**Install:**

```bash
npx shadcn@latest add @designrevision/progress-upload-01
```

**Dependencies:** progress

```tsx
// components/ui/progress-upload-01.tsx
"use client"

import * as React from "react"

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

export default function ProgressUpload01() {
  const [value, setValue] = React.useState(0)

  React.useEffect(() => {
    const interval = setInterval(() => {
      setValue((v) => (v >= 100 ? 0 : v + 5))
    }, 300)
    return () => clearInterval(interval)
  }, [])

  return (
    <div className="w-full max-w-sm space-y-2">
      <div className="flex items-center justify-between text-sm">
        <span className="text-muted-foreground">document.pdf</span>
        <span className="font-medium">{value}%</span>
      </div>
      <Progress value={value} />
    </div>
  )
}
```

---

### Circular progress

A circular ring built with SVG `stroke-dashoffset`, with a centered percentage.

**Install:**

```bash
npx shadcn@latest add @designrevision/progress-circular-01
```

**Dependencies:** progress

```tsx
// components/ui/progress-circular-01.tsx
// A circular progress ring built with SVG stroke-dashoffset — beyond the linear Progress bar.
export default function ProgressCircular01() {
  const value = 68
  const size = 96
  const stroke = 8
  const radius = (size - stroke) / 2
  const circumference = 2 * Math.PI * radius
  const offset = circumference - (value / 100) * circumference

  return (
    <div className="relative inline-flex items-center justify-center">
      <svg width={size} height={size} className="-rotate-90">
        <circle
          cx={size / 2}
          cy={size / 2}
          r={radius}
          fill="none"
          strokeWidth={stroke}
          className="stroke-primary/20"
        />
        <circle
          cx={size / 2}
          cy={size / 2}
          r={radius}
          fill="none"
          strokeWidth={stroke}
          strokeLinecap="round"
          strokeDasharray={circumference}
          strokeDashoffset={offset}
          className="stroke-primary transition-all"
        />
      </svg>
      <span className="absolute text-lg font-semibold">{value}%</span>
    </div>
  )
}
```
