# Progress

Displays an indicator showing the completion progress of a task.

```tsx
"use client"

import { useEffect, useState } from "react"
import * as stylex from "@stylexjs/stylex"
import { Progress } from "@/components/ui/progress"

const styles = stylex.create({
  progress: { width: "100%", maxWidth: "24rem" },
})

export default function ProgressDemo() {
  const [value, setValue] = useState(13)

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

  return <Progress value={value} xstyle={styles.progress} />
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex clsx
   ```

2. Set up the token layer and the StyleX compiler, if you have not already. See
   [Installation](https://carbon.style/docs/installation), and paste `state-styles.ts` from the same
   page. The indicator reads Base UI's state through it.

3. Copy and paste the following code into your project.

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

   import type { CSSProperties } from "react"
   import { Progress as ProgressPrimitive } from "@base-ui/react/progress"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"
   import { clsx } from "clsx"

   import { stateStyles } from "@/lib/state-styles"
   import {
     color,
     duration,
     ease,
     leading,
     radius,
     shadow,
     space,
     text,
   } from "@/theme/tokens.stylex"

   const sweep = stylex.keyframes({
     from: { transform: "translateX(-100%)" },
     to: { transform: "translateX(400%)" },
   })

   const pulse = stylex.keyframes({
     "0%": { opacity: 0.35 },
     "50%": { opacity: 1 },
     "100%": { opacity: 0.35 },
   })

   const styles = stylex.create({
     root: {
       display: "flex",
       flexWrap: "wrap",
       gap: space.s3,
     },
     track: {
       position: "relative",
       display: "flex",
       alignItems: "center",
       width: "100%",
       height: space.s1,
       borderRadius: radius.full,
       backgroundColor: color.muted,
       boxShadow: shadow.groove,
       overflowX: "hidden",
     },
     indicator: {
       height: "100%",
       borderRadius: radius.full,
       backgroundColor: color.primary,
       backgroundImage: `linear-gradient(to bottom, ${color.buttonPrimarySheen}, transparent)`,
       transitionProperty: "width",
       transitionDuration: {
         default: duration.panel,
         "@media (prefers-reduced-motion: reduce)": "0s",
       },
       transitionTimingFunction: ease.spring,
     },
     indeterminate: {
       width: { default: "25%", "@media (prefers-reduced-motion: reduce)": "100%" },
       animationName: { default: sweep, "@media (prefers-reduced-motion: reduce)": pulse },
       animationDuration: { default: "1.2s", "@media (prefers-reduced-motion: reduce)": "2s" },
       animationTimingFunction: {
         default: "linear",
         "@media (prefers-reduced-motion: reduce)": ease.standard,
       },
       animationIterationCount: "infinite",
     },
     label: {
       fontSize: text.sm,
       lineHeight: leading.sm,
       fontWeight: 500,
     },
     value: {
       marginInlineStart: "auto",
       color: color.mutedForeground,
       fontSize: text.sm,
       lineHeight: leading.sm,
       fontVariantNumeric: "tabular-nums",
     },
   })

   const progressStyles = {
     root: styles.root,
     track: styles.track,
     indicator: styles.indicator,
     indeterminate: styles.indeterminate,
     label: styles.label,
     value: styles.value,
   }

   type StyleProps = {
     xstyle?: StyleXStyles
     className?: string
   }

   type StateOverrides = StyleProps & { style?: CSSProperties }

   type ProgressProps = Omit<ProgressPrimitive.Root.Props, "className"> & StyleProps

   function Progress({ xstyle, className, children, ...props }: ProgressProps) {
     const sx = stylex.props(styles.root, xstyle)

     return (
       <ProgressPrimitive.Root
         data-slot="progress"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       >
         {children}
         <ProgressTrack>
           <ProgressIndicator />
         </ProgressTrack>
       </ProgressPrimitive.Root>
     )
   }

   function ProgressTrack({
     xstyle,
     className,
     ...props
   }: Omit<ProgressPrimitive.Track.Props, "className"> & StyleProps) {
     const sx = stylex.props(styles.track, xstyle)

     return (
       <ProgressPrimitive.Track
         data-slot="progress-track"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   function ProgressIndicator({
     xstyle,
     style,
     className,
     ...props
   }: Omit<ProgressPrimitive.Indicator.Props, "className" | "style"> & StateOverrides) {
     return (
       <ProgressPrimitive.Indicator
         data-slot="progress-indicator"
         {...props}
         {...stateStyles(
           (state) => [
             styles.indicator,
             state.status === "indeterminate" && styles.indeterminate,
             xstyle,
           ],
           { className, style },
         )}
       />
     )
   }

   function ProgressLabel({
     xstyle,
     className,
     ...props
   }: Omit<ProgressPrimitive.Label.Props, "className"> & StyleProps) {
     const sx = stylex.props(styles.label, xstyle)

     return (
       <ProgressPrimitive.Label
         data-slot="progress-label"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   function ProgressValue({
     xstyle,
     className,
     ...props
   }: Omit<ProgressPrimitive.Value.Props, "className"> & StyleProps) {
     const sx = stylex.props(styles.value, xstyle)

     return (
       <ProgressPrimitive.Value
         data-slot="progress-value"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   export { Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, progressStyles }
   ```

4. Update the import paths to match your project setup.

## Usage

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

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

`value` is required and runs from `min` to `max`, which default to `0` and
`100`. `Progress` renders the track and the indicator itself, so children sit
above the bar.

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

## Label

Use `ProgressLabel` to name the task and `ProgressValue` to print the number.
The value is pushed to the end of the row and the track wraps onto its own line
below them.

```tsx
import * as stylex from "@stylexjs/stylex"
import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress"

const styles = stylex.create({
  progress: { width: "100%", maxWidth: "24rem" },
})

export default function ProgressLabelDemo() {
  return (
    <Progress value={56} xstyle={styles.progress}>
      <ProgressLabel>Upload progress</ProgressLabel>
      <ProgressValue />
    </Progress>
  )
}
```

With no `format`, `ProgressValue` divides the value by 100 and prints it as a
percentage. Give it a function child to render something else from the same
numbers.

```tsx
<ProgressValue>{(formatted, value) => `${value} of 100 files`}</ProgressValue>
```

## Controlled

Pass a new `value` to move the bar. The indicator transitions its width over
300ms, so a jump of any size animates instead of snapping.

```tsx
"use client"

import { useState } from "react"
import * as stylex from "@stylexjs/stylex"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress"
import { VStack } from "@/components/ui/stack"

const styles = stylex.create({
  panel: { width: "100%", maxWidth: "24rem" },
  progress: { width: "100%" },
})

const clamp = (value: number) => Math.min(100, Math.max(0, value))

export default function ProgressControlled() {
  const [value, setValue] = useState(40)

  return (
    <VStack gap={4} align="start" xstyle={styles.panel}>
      <Progress value={value} xstyle={styles.progress}>
        <ProgressLabel>Transfer</ProgressLabel>
        <ProgressValue />
      </Progress>
      <ButtonGroup>
        <Button variant="outline" onClick={() => setValue(clamp(value - 10))}>
          -10
        </Button>
        <Button variant="outline" onClick={() => setValue(clamp(value + 10))}>
          +10
        </Button>
        <Button variant="outline" onClick={() => setValue(0)}>
          Reset
        </Button>
      </ButtonGroup>
    </VStack>
  )
}
```

## Indeterminate

Pass `value={null}` for a task with no measurable progress. The indicator
becomes a quarter-width bar sweeping the track, `ProgressValue` renders
nothing, and Base UI writes `data-indeterminate` on every part.

```tsx
import * as stylex from "@stylexjs/stylex"
import { Progress, ProgressLabel } from "@/components/ui/progress"

const styles = stylex.create({
  progress: { width: "100%", maxWidth: "24rem" },
})

export default function ProgressIndeterminate() {
  return (
    <Progress value={null} xstyle={styles.progress}>
      <ProgressLabel>Preparing export</ProgressLabel>
    </Progress>
  )
}
```

> **Note**
>
> Under `prefers-reduced-motion: reduce` the sweep becomes a full-width bar that
> fades in and out. Motion is the only way an indeterminate bar shows that work
> is happening, so the component replaces it rather than dropping it.

## Formatting

Use `format` to pass `Intl.NumberFormat` options, which replaces the default
percentage, and `locale` to pick the locale, which defaults to the runtime's.

```tsx
<Progress value={3400} max={5000} format={{ style: "currency", currency: "USD" }}>
  <ProgressLabel>Raised</ProgressLabel>
  <ProgressValue />
</Progress>
```

## API reference

Pass `render` to replace a part's element or compose it with another component.

Every part wraps the Base UI `Progress` part of the same name and accepts its
props. See the [Base UI reference](https://base-ui.com/react/components/progress).
Each part also takes `xstyle` and `className`. `ProgressIndicator` styles itself
from Base UI's state, so it takes `style` as well. It is real inline CSS, merged
over the compiled style alongside the width Base UI writes.

### Progress

| Prop               | Type                                                | Default |
| ------------------ | --------------------------------------------------- | ------- |
| `value`            | `number \| null`                                    | —       |
| `min`              | `number`                                            | `0`     |
| `max`              | `number`                                            | `100`   |
| `format`           | `Intl.NumberFormatOptions`                          | —       |
| `locale`           | `Intl.LocalesArgument`                              | —       |
| `getAriaValueText` | `(formattedValue: string \| null, value) => string` | —       |
| `xstyle`           | `StyleXStyles`                                      | —       |
| `className`        | `string`                                            | —       |

The root is a `<div role="progressbar">`. It renders `ProgressTrack` and
`ProgressIndicator` after its children, so a bar with no label needs no
children at all.

### ProgressValue

| Prop       | Type                                                              | Default |
| ---------- | ----------------------------------------------------------------- | ------- |
| `children` | `(formattedValue: string \| null, value: number \| null) => Node` | —       |

The function child receives `"indeterminate"` as its first argument while
`value` is `null`.

### progressStyles

The style maps behind the component. Spread them onto any element.

| Key             | What it is                                       |
| --------------- | ------------------------------------------------ |
| `root`          | The wrapping row: flex, wrap, gap                |
| `track`         | The groove: height, fill, inset shadow, clipping |
| `indicator`     | The lit bar and its width transition             |
| `indeterminate` | The sweep, and its reduced-motion replacement    |
| `label`         | Label type                                       |
| `value`         | Muted, tabular type pushed to the end            |

### Data attributes

| Attribute                                                  | On         | Effect                             |
| ---------------------------------------------------------- | ---------- | ---------------------------------- |
| `data-slot="progress"`                                     | the root   | Targets the row from a parent      |
| `data-slot="progress-track"`                               | the track  | Targets the groove                 |
| `data-slot="progress-indicator"`                           | the bar    | Targets the fill                   |
| `data-slot="progress-label"`, `data-slot="progress-value"` | the text   | Targets the label and the number   |
| `data-indeterminate`, `data-progressing`, `data-complete`  | every part | Base UI writes them from the value |
