Qure UI
Components

Progress

Shows how far along a task is.

x
x
import { Progress } from '@/registry/qure/ui/progress'export default function ProgressDemo() {  return (    <div className="flex w-full max-w-sm flex-col gap-4">      <Progress value={28} />      <Progress value={72} />    </div>  )}

Installation

npx shadcn@latest add https://qure-ui.qure.ai/r/progress.json

Usage

import { Progress } from '@/components/ui/progress'
<Progress value={72} aria-label="Studies analysed" />

value is required and runs 0–100 by default; pass min and max for another range. The primitive keeps role="progressbar" and the aria-value* attributes in step, so the number is announced without you writing any of it.

Progress is for work whose extent is known and whose end the user is waiting for. It is the wrong control for a proportion that is simply a fact — beds occupied, storage used, agreement between two readers. That is a meter, and dressing it as progress implies it is heading somewhere.

Determinate

x
x
import { Progress } from '@/registry/qure/ui/progress'export default function ProgressDemo() {  return (    <div className="flex w-full max-w-sm flex-col gap-4">      <Progress value={28} />      <Progress value={72} />    </div>  )}

Two bars at 28% and 72%. The indicator's width is set by the primitive and transitions over 240ms, so a value arriving in steps animates rather than jumps.

With a label and a value

Uploading series 3 of 1464%
x
import { Progress } from '@/registry/qure/ui/progress'export default function ProgressLabel() {  return (    <div className="grid w-full max-w-sm gap-2">      <div className="flex items-baseline justify-between text-sm">        <span id="upload-label">Uploading series 3 of 14</span>        <span className="tabular-nums text-muted-foreground">64%</span>      </div>      <Progress value={64} aria-labelledby="upload-label" />    </div>  )}

The bar carries no text of its own. Put the name and the number in a row above it and point the bar at the name with aria-labelledby — a bare bar with a percentage floating near it is two unrelated things to a screen reader. Use tabular-nums on the figure so it does not jitter as it counts.

A count rather than a percentage

Series processed
x
14 / 32
import { Progress } from '@/registry/qure/ui/progress'export default function ProgressRange() {  return (    <div className="grid w-full max-w-sm gap-2">      <span className="text-sm" id="series-label">Series processed</span>      <Progress        value={14}        max={32}        aria-labelledby="series-label"        getAriaValueText={(_, value) => `${value} of 32 series processed`}      />      <span className="text-sm tabular-nums text-muted-foreground">14 / 32</span>    </div>  )}

Set max to the real total and pass the real count. "14 of 32 series" is more use than 44%, and getAriaValueText puts the same sentence into the announcement rather than leaving a screen reader to read out a bare number.

Inside a card

Overnight qXR batch
412 of 640 studies analysed
x

About 12 minutes remaining.

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/qure/ui/card'import { Progress } from '@/registry/qure/ui/progress'export default function ProgressCard() {  return (    <Card className="w-full max-w-sm">      <CardHeader>        <CardTitle>Overnight qXR batch</CardTitle>        <CardDescription>412 of 640 studies analysed</CardDescription>      </CardHeader>      <CardContent className="grid gap-2">        <Progress value={412} max={640} aria-label="Studies analysed" />        <p className="text-sm text-muted-foreground">About 12 minutes remaining.</p>      </CardContent>    </Card>  )}

Where the job has a name, a subject and an estimate, those belong in a card and the bar is one line of it. aria-label is enough here because the card title already says what is running.

Indeterminate

Pass value={null} when you cannot know the proportion — an upload that has not reported a size, a job with no total. Do not fake it with an animation that implies a percentage.

Indeterminate is announced correctly (data-indeterminate lands on the root and the value text becomes "indeterminate progress") but it has no look yet: the primitive leaves the indicator without a width, and our stylesheet has no [data-indeterminate] rule, so the track renders empty. Until it does, use a spinner for unknown-length work.

For something that finishes in under a second, no indicator at all is better than one that flashes. A progress bar that appears and vanishes reads as a glitch.

API Reference

Everything Base UI's Progress accepts. Our wrapper renders the Root, Track and Indicator itself, so className reaches the root and the parts are reached through their data-slot. The ones you will use:

Prop

Type

On this page