Progress
How far along a task is: a neutral track with a bar in the tone, read by a screen reader as a progressbar from 0 to 100.
server - 0 KB
Progress
server - 0 KBHow far along a task is: a neutral track with a bar in the tone, read by a screen reader as a progressbar from 0 to 100.
Try it
value
tone
size
indeterminate
The code
progress.tsx
import { Progress } from 'topclassui/progress'
<div className="grid max-w-sm">
<Progress aria-label="Upload" />
</div>It renders no text and clamps value to 0-100, so name it with aria-label or aria-labelledby.
Every state
5 statesEmpty
Part way
Complete
Indeterminate
Tones
Import from the package
topclassui/progress
import { Progress } from 'topclassui/progress'Install once:npm i topclassui@beta zod
Examples
- For an upload, a quota or a step in a long job.
valueis a percentage, clamped to 0-100 before it reaches ARIA or the bar; for a custom range, use Meter. indeterminateis for a wait of unknown length: no value is announced and a short bar pulses.sizesets the track height from the spacing scale, so it follows density;customemits none and your class sets it.- The track fills its container. Give it a fixed width with
className, as a bar in a file row does.
labelled
Storage: 7.2 GB of 10 GB
labelled.tsx
import { Progress } from 'topclassui/progress'
/** The docs sample: the visible text names the bar, and the bar announces 72. */
export default function Demo() {
return (
<div className="grid max-w-sm gap-2">
<span id="storage-label" className="text-sm">Storage: 7.2 GB of 10 GB</span>
<Progress value={72} tone="warning" aria-labelledby="storage-label" />
</div>
)
}
values
Not started
Uploading
Halfway
Almost there
Done
values.tsx
import { Progress } from 'topclassui/progress'
/** The values the frame draws, from empty to complete, each named by the text above it. */
export default function Demo() {
return (
<div className="grid max-w-sm gap-4">
<div className="grid gap-1">
<span id="step-empty" className="text-sm">Not started</span>
<Progress value={0} aria-labelledby="step-empty" />
</div>
<div className="grid gap-1">
<span id="step-early" className="text-sm">Uploading</span>
<Progress value={15} aria-labelledby="step-early" />
</div>
<div className="grid gap-1">
<span id="step-half" className="text-sm">Halfway</span>
<Progress value={50} aria-labelledby="step-half" />
</div>
<div className="grid gap-1">
<span id="step-late" className="text-sm">Almost there</span>
<Progress value={85} aria-labelledby="step-late" />
</div>
<div className="grid gap-1">
<span id="step-done" className="text-sm">Done</span>
<Progress value={100} aria-labelledby="step-done" />
</div>
</div>
)
}
tones
tones.tsx
import { Progress } from 'topclassui/progress'
export default function Demo() {
return (
<div className="grid max-w-sm gap-4">
<Progress tone="primary" value={60} aria-label="Primary" />
<Progress tone="neutral" value={60} aria-label="Neutral" />
<Progress tone="success" value={60} aria-label="Success" />
<Progress tone="warning" value={60} aria-label="Warning" />
<Progress tone="danger" value={60} aria-label="Danger" />
<Progress tone="info" value={60} aria-label="Info" />
</div>
)
}
sizes
sizes.tsx
import { Progress } from 'topclassui/progress'
export default function Demo() {
return (
<div className="grid max-w-sm gap-4">
<Progress size="xs" value={60} aria-label="Extra small" />
<Progress size="sm" value={60} aria-label="Small" />
<Progress size="md" value={60} aria-label="Medium" />
<Progress size="lg" value={60} aria-label="Large" />
<Progress size="xl" value={60} aria-label="Extra large" />
</div>
)
}
indeterminate
indeterminate.tsx
import { Progress } from 'topclassui/progress'
/** A wait of unknown length: no value is announced, and a third of the track pulses. */
export default function Demo() {
return (
<div className="grid max-w-sm gap-4">
<Progress indeterminate aria-label="Loading the report" />
<Progress indeterminate size="sm" tone="neutral" aria-label="Syncing" />
</div>
)
}
file-row
brief.pdf
file-row.tsx
import { Progress } from 'topclassui/progress'
/** The real consumer: a 4 px bar at a fixed width under a file name, which names it. */
export default function Demo() {
return (
<div className="flex max-w-sm flex-col gap-2 rounded-box border border-line p-3">
<span id="file-brief" className="truncate text-sm">brief.pdf</span>
<Progress size="sm" value={65} className="w-50 max-w-full" aria-labelledby="file-brief" />
</div>
)
}
custom-size
custom-size.tsx
import { Progress } from 'topclassui/progress'
/** custom emits no height, so the caller's own class sets it. */
export default function Demo() {
return (
<div className="grid max-w-sm gap-4">
<Progress value={40} aria-label="On the scale" />
<Progress size="custom" value={40} className="h-3" aria-label="Your own height" />
</div>
)
}
In Arabic, right to left
رفع الملف
rtl.tsx
import { Progress } from 'topclassui/progress'
/** The bar grows from the right, the inline start, with no class of its own for it. */
export default function Demo() {
return (
<div dir="rtl" lang="ar" className="grid max-w-sm gap-4">
<div className="grid gap-1">
<span id="upload-ar" className="text-sm">رفع الملف</span>
<Progress value={60} aria-labelledby="upload-ar" />
</div>
<Progress value={30} tone="success" aria-label="النسخ الاحتياطي" />
<Progress indeterminate aria-label="جارٍ التحميل" />
</div>
)
}
Props
Keyboard and accessibility
Nothing to operate: it is not interactive on its own.
- A name is required: the type refuses a Progress with neither
aria-labelnoraria-labelledby. - It writes
role="progressbar"with a minimum of 0, a maximum of 100 and the clamped value; an indeterminate bar carries no value at all. - The bar takes the tone's text colour and a
line-strongoutline marks the end of the range. Under forced colours the track is Canvas and the bar CanvasText, and reduced motion stops the pulse.
Right to left
The bar grows from the inline start: from the right in Arabic, with no class of its own for it.
Server and client
Renders on the server and ships no JavaScript.
Files: index.tsx.
On the chip: server - 0 KB.