Progress
How far a task has got, as a bar or a ring: with a value when you know it, sweeping or spinning when you do not.
- Press Upload again: the bar eases to each new value. With reduced motion it jumps.
- Without a value the bar sweeps across and the ring spins; screen readers hear no number.
formatValuewrites the text shown and read out: "3 of 5" instead of "60%".
Field marks
- Import
import { Progress, CircularProgress } from "cennge-ui";- Size
- 1.8 KB gzip for this import, its dependencies aside
- Pattern
role="progressbar"witharia-valuetext; noaria-valuenowwhile indeterminate- State
- Controlled: pass
value, or leave it out until you know it - SSR
- Rendered on the server in the Next.js check; hydrates without a warning
- Reduced motion
- The bar stops sweeping and shows dimmed at full width; the ring turns more slowly
- Tests
- Unit tests
- Source
- src/components/progress
When to use#
Use Progress for a task with an end: an upload, a clone, a run of CI jobs. Progress is the bar for a row or a form; CircularProgress is the ring for a tile or a tight corner. Until you know how much is done, leave value out: both still say that work is under way.
Similar#
- Spinner is a small turning ring with no value and no progressbar role, sized to sit in a line of text.
- Skeleton stands in for content whose shape you already know.
- Stepper counts named steps a person moves through, not the share of a task that is done.
Any scale, any text#
value counts up to max, 100 unless you set it. With a label the value shows beside it, as formatValue writes it, and screen readers read the same text. Without a visible label, name the bar with aria-label.
import { Progress } from "cennge-ui";
const count = (value: number) => value.toLocaleString("en-US");
export default function ProgressScale() {
return (
<div className="flex w-full max-w-md flex-col gap-6">
<Progress
label="Release assets"
value={3}
max={8}
formatValue={(value, max) => `${value} of ${max} uploaded`}
/>
<Progress
label="Actions minutes this month"
value={1540}
max={2000}
color="warning"
size="lg"
formatValue={(value, max) => `${count(value)} of ${count(max)}`}
/>
</div>
);
}Still working#
Leave value out, or pass null, while the amount is unknown. Pass a number once it is, and the bar or ring changes in place.
import { CircularProgress, Progress } from "cennge-ui";
export default function StillWorking() {
return (
<div className="flex w-full flex-wrap items-center gap-x-10 gap-y-6">
<Progress label="Cloning playwright-msi-scraper" className="max-w-sm" />
<CircularProgress label="Installing dependencies" />
</div>
);
}Circular#
Four sizes, from 32 to 96 pixels. showValue puts the value in the middle; the label sits under the ring and names it for screen readers. strokeWidth changes the thickness of the ring.
import { CircularProgress, type CircularProgressSize } from "cennge-ui";
const sizes: { size: CircularProgressSize; pixels: number }[] = [
{ size: "sm", pixels: 32 },
{ size: "md", pixels: 48 },
{ size: "lg", pixels: 64 },
{ size: "xl", pixels: 96 },
];
export default function CircularSizes() {
return (
<div className="flex flex-wrap items-end gap-8">
{sizes.map(({ size, pixels }, index) => (
<CircularProgress
key={size}
label={`${size}, ${pixels} px`}
value={25 * (index + 1)}
showValue={size !== "sm"}
size={size}
/>
))}
</div>
);
}