Progress
Displays the completion status of a task.
Export data
Waiting for server
Uploading
Codeapp/demos/public/progress.tsx
import { clientEntry, on, type Handle } from 'remix/component'
import { Progress, ProgressLabel, ProgressValue } from '@stemsui/registry/ui/progress.tsx'
export const ProgressDemo = clientEntry(import.meta.url, function ProgressDemo(handle: Handle) {
let value = 13
let running = true
// Browser-only: advance the bar over time.
handle.queueTask(() => {
let timer = setInterval(() => {
if (!running) return
value = value >= 100 ? 0 : Math.min(100, value + Math.round(Math.random() * 15 + 5))
void handle.update()
}, 800)
handle.signal.addEventListener('abort', () => clearInterval(timer))
})
return () => (
<div class="flex w-full max-w-sm flex-col gap-8">
<Progress value={value}>
<ProgressLabel>Export data</ProgressLabel>
<ProgressValue />
</Progress>
<Progress value={null}>
<ProgressLabel>Waiting for server</ProgressLabel>
</Progress>
<Progress value={3} max={10} getAriaValueText={(_, current) => `${current} of 10 files`}>
<ProgressLabel>Uploading</ProgressLabel>
<ProgressValue>3 of 10 files</ProgressValue>
</Progress>
<button
type="button"
class="self-start text-sm text-muted-foreground underline underline-offset-4"
mix={on('click', () => {
running = !running
void handle.update()
})}
>
{running ? 'Pause' : 'Resume'}
</button>
</div>
)
})
Installation
npx shadcn add @stemsui/progressRequires the one-time setup. Behavior: stems-ui/progress.
API reference
The styled component is built on the headless module below: its props mirror these options, and the same events and data attributes apply.
stems-ui/progress
progress displays the status of a task that takes a long time, as an accessible progress bar.
Base UI equivalent: Progress.
Usage
import * as progress from 'stems-ui/progress'
function Export(handle: Handle<{ value: number | null }>) {
return () => (
<progress.Context value={handle.props.value}>
<div class="grid grid-cols-2" mix={progress.root()}>
<span mix={progress.label()}>Export data</span>
<span class="text-right" mix={progress.value()}>
<progress.ValueText />
</span>
<div class="col-span-2 h-1 bg-muted" mix={progress.track()}>
<div class="h-full bg-primary transition-[width]" mix={progress.indicator()} />
</div>
</div>
</progress.Context>
)
}
stems-ui/progress
<progress.Context>props:value:number | null.nullmeans indeterminate.min: defaults to0.max: defaults to100.format:Intl.NumberFormatOptionsfor the value text.locale:Intl.LocalesArgument. Defaults to the runtime locale.getAriaValueText(formattedValue, value): returnsaria-valuetext.
progress.root():role="progressbar",aria-valuenow(omitted when indeterminate),aria-valuemin,aria-valuemax,aria-valuetext, andaria-labelledbypointing atlabel()(skipped when the host hasaria-label).progress.track(): the track. Status attributes only.progress.indicator(): sets an inline stylewidth: <percentage>%(plus Base UI'sinset-inline-start: 0; height: inherit), merged with the hoststyle. No width when indeterminate.progress.value(): the host for the value text. Setsaria-hidden="true"(the root already exposes the value).progress.label(): sets theidthe root'saria-labelledbypoints at.<progress.ValueText>: renders the formatted value as a text node, with no element of its own. Put it inside thevalue()host. Pass a function child to customize it:{(formattedValue, value) => …}(formattedValueisnullwhen indeterminate).formatValue(value, { format, locale }): the formatter, for use outside a Context.
Behavior notes
- Mixins can't render children, so the value text comes from
<progress.ValueText>(a hostless component reading the Context) orformatValue(). - Formatting follows Base UI: without
format, the value is formatted as a percent of 100 (value / 100withstyle: 'percent'), whatevermin/maxare. Passformatfor other units. - Default
aria-valuetext: the formatted value, or"indeterminate progress". - The status is
completewhenvalue === max,progressingfor other finite values, andindeterminatefornull. - Server rendering: Base UI uses
en-USuntil hydration. Stems uses the runtime locale on both sides, so passlocaleif the server and browser locales might differ. - The root's
aria-labelledbyis always set (unless the host hasaria-label). If there is nolabel()element, the reference doesn't resolve and assistive technology ignores it.
Styling hooks
All parts (root, track, indicator, value, label):
| Attribute | Values |
|---|---|
data-complete | Present when the progress has completed. |
data-progressing | Present while the progress is ongoing. |
data-indeterminate | Present when value is null. |