Show completion with a plain bar, a labeled live value, or a value driven by another control.
"use client"import * as React from "react"import { Button } from "@/components/ui/button"import { Progress } from "@/components/ui/progress"const DURATION_MS = 2800export function Example() { const [run, setRun] = React.useState(0) const progress = useProgressRun(run) return ( <div className="relative w-full"> <Button type="button" variant="ghost" size="icon-sm" className="absolute top-0 right-0 z-20" aria-label="Reload" onClick={() => setRun((n) => n + 1)} > <ReloadIcon /> </Button> <Progress value={progress} className="w-full [&_[data-slot=progress-indicator]]:duration-0" /> </div> )}function useProgressRun(run: number) { const [progress, setProgress] = React.useState(0) React.useEffect(() => { setProgress(0) const start = performance.now() let frame = 0 function tick(now: number) { const t = Math.min(1, (now - start) / DURATION_MS) setProgress(Math.round(t * 100)) if (t < 1) frame = requestAnimationFrame(tick) } frame = requestAnimationFrame(tick) return () => cancelAnimationFrame(frame) }, [run]) return progress}function ReloadIcon() { return ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="size-3.5" aria-hidden > <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /> <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" /> <path d="M8 16H3v5" /> </svg> )}Installation
npx @arctis-sh/@arctis-sh/ui@latest add progressUsage
import { Progress } from "@/components/ui/progress"<Progress value={33} />Composition
Progress
├── ProgressLabel
├── ProgressValue
└── ProgressTrack
└── ProgressIndicatorProgressTrack and ProgressIndicator are included automatically. Add ProgressLabel and ProgressValue when you want a header row.
Basic
A plain bar from 0 to 100. Reload the example to replay the fill animation.
"use client"import * as React from "react"import { Button } from "@/components/ui/button"import { Progress } from "@/components/ui/progress"const DURATION_MS = 2800export function Example() { const [run, setRun] = React.useState(0) const progress = useProgressRun(run) return ( <div className="relative w-full"> <Button type="button" variant="ghost" size="icon-sm" className="absolute top-0 right-0 z-20" aria-label="Reload" onClick={() => setRun((n) => n + 1)} > <ReloadIcon /> </Button> <Progress value={progress} className="w-full [&_[data-slot=progress-indicator]]:duration-0" /> </div> )}function useProgressRun(run: number) { const [progress, setProgress] = React.useState(0) React.useEffect(() => { setProgress(0) const start = performance.now() let frame = 0 function tick(now: number) { const t = Math.min(1, (now - start) / DURATION_MS) setProgress(Math.round(t * 100)) if (t < 1) frame = requestAnimationFrame(tick) } frame = requestAnimationFrame(tick) return () => cancelAnimationFrame(frame) }, [run]) return progress}function ReloadIcon() { return ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="size-3.5" aria-hidden > <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /> <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" /> <path d="M8 16H3v5" /> </svg> )}Label
Add ProgressLabel and ProgressValue for a title and live percentage.
"use client"import * as React from "react"import { Button } from "@/components/ui/button"import { Progress, ProgressLabel, ProgressValue,} from "@/components/ui/progress"const DURATION_MS = 2800export function Example() { const [run, setRun] = React.useState(0) const progress = useProgressRun(run) return ( <div className="relative w-full"> <Button type="button" variant="ghost" size="icon-sm" className="absolute top-0 right-0 z-20" aria-label="Reload" onClick={() => setRun((n) => n + 1)} > <ReloadIcon /> </Button> <Progress value={progress} className="w-full [&_[data-slot=progress-indicator]]:duration-0" > <ProgressLabel>Upload progress</ProgressLabel> <ProgressValue /> </Progress> </div> )}function useProgressRun(run: number) { const [progress, setProgress] = React.useState(0) React.useEffect(() => { setProgress(0) const start = performance.now() let frame = 0 function tick(now: number) { const t = Math.min(1, (now - start) / DURATION_MS) setProgress(Math.round(t * 100)) if (t < 1) frame = requestAnimationFrame(tick) } frame = requestAnimationFrame(tick) return () => cancelAnimationFrame(frame) }, [run]) return progress}function ReloadIcon() { return ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="size-3.5" aria-hidden > <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /> <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" /> <path d="M8 16H3v5" /> </svg> )}Controlled
Drive the bar from another control. This example uses a Slider.
"use client"import * as React from "react"import { Progress, ProgressLabel, ProgressValue,} from "@/components/ui/progress"import { Slider } from "@/components/ui/slider"export function Example() { const [value, setValue] = React.useState([40]) return ( <div className="grid w-full gap-4"> <Progress value={value[0] ?? 0} className="w-full [&_[data-slot=progress-indicator]]:duration-0" > <ProgressLabel>Export</ProgressLabel> <ProgressValue /> </Progress> <Slider value={value} onValueChange={(next) => { const nextValue = readSliderValue(next) setValue((prev) => (prev[0] === nextValue ? prev : [nextValue])) }} min={0} max={100} step={1} /> </div> )}function readSliderValue(next: number | readonly number[]) { if (typeof next === "number") return next if (Array.isArray(next)) return next[0] ?? 0 return 0}API Reference
Progress
value is clamped between 0 and max.
| Prop | Type | Default |
|---|---|---|
| value | number | 0 |
| max | number | 100 |
| className | string | — |
<Progress value={60} max={100} />ProgressLabel
Sets aria-labelledby on the progressbar when present.
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<Progress> <ProgressLabel>Uploading</ProgressLabel> <ProgressValue /></Progress>ProgressValue
Defaults to a rounded percentage. Pass children or a render function to customize.
| Prop | Type | Default |
|---|---|---|
| children | ReactNode | ((value: number, max: number) => ReactNode) | — |
| className | string | — |
<ProgressValue />ProgressTrack
Optional. Rendered automatically when omitted.
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<ProgressTrack> <ProgressIndicator /></ProgressTrack>ProgressIndicator
Optional. Rendered automatically inside the track when omitted. Width follows value / max. Defaults to a duration-300 width transition — override with className when you want instant updates.
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<ProgressIndicator />