Skip to content
cennge-ui
GitHubv0.3.1, changelog

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.

payload-cache-1.4.0.tgz64%
Cloning spredo-crypto-screener
CI jobs
Waiting for a runner
  1. Press Upload again: the bar eases to each new value. With reduced motion it jumps.
  2. Without a value the bar sweeps across and the ring spins; screen readers hear no number.
  3. formatValue writes the text shown and read out: "3 of 5" instead of "60%".
Live: run the upload.

Field marks

Import
import { Progress, CircularProgress } from "cennge-ui";
Size
1.8 KB gzip for this import, its dependencies aside
Pattern
role="progressbar" with aria-valuetext; no aria-valuenow while 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

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.

Release assets3 of 8 uploaded
Actions minutes this month1,540 of 2,000
scale.tsx
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.

Cloning playwright-msi-scraper
Installing dependencies
indeterminate.tsx
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.

sm, 32 px
md, 48 px
lg, 64 px
xl, 96 px
circular.tsx
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>
  );
}

Props#

Progress#

PropTypeDefault
value
number | nullOmit (or pass null) for an indeterminate bar.
–
max
number
100
label
React.ReactNode
–
showValue
boolean
true when there is a label and a value
formatValue
(value: number, max: number) => string
(v, max) => `${Math.round((v / max) * 100)}%`
color
"default""primary""secondary""success""warning""danger""info"
"primary"
size
"sm""md""lg"
"md"
className
string
–
aria-label
string
–

CircularProgress#

PropTypeDefault
value
number | nullOmit (or pass null) for an indeterminate spinner.
–
max
number
100
label
React.ReactNodeText under the circle. Also names the progress bar for screen readers.
–
showValue
booleanShow the value in the middle.
false
formatValue
(value: number, max: number) => string
(v, max) => `${Math.round((v / max) * 100)}%`
color
"default""primary""secondary""success""warning""danger""info"
"primary"
size
"sm""md""lg""xl"
"md"
strokeWidth
numberRing thickness in px. Defaults to a value that suits the size.
–
className
string
–
aria-label
string
–