Progress Log
betaThe steps of a long piece of work, like an import, a setup, or a deploy. The list grows while it runs, then folds into one line, like Done in 34s, 5 steps. A failure stays open and says why.
Preview
live
Uploaded contacts.csv (working)
loading
Built 48 pages
Resizing images (working)
done
opened
Uploaded contacts.csv
Checked every row
Merged 64 duplicates
Added tags from the Source column
Saved 2,318 contacts
failed
The card was declined. Try another card.
Made the account
Picked the Team plan
Charging the card (failed)
summary
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/progress-log
Use it for
- Show the steps of work that takes a while, like importing a file, setting up an account, or publishing a site.
- Pass loading while it runs. Add each step to the end of steps as it starts. The last one spins.
- Set loading to false when it is done. The list folds into one line that opens again.
- Pass total when you know how many steps there are. The line then counts them, like Step 3 of 5.
- If it stops, set loading to false and pass error in plain words. The last step turns red, and the list stays open.
- Pass duration, like 34s, and keep it current while loading for a live timer. The folded line uses it.
- Give each step an id, so a new one fades in and the rest stay still.
Avoid it for
- A single wait with no steps. Use Spinner.
- One job with its details and result. Use JobCard.
- Progress you can measure, like 3 of 5 files sent, with no steps to show. Use Progress.
- An agent's own steps while it thinks. Use ThinkingState.
Props
- steps
Required. The steps so far, oldest first. While loading, the last one is under way.
{ id?: string; label: ReactNode; detail?: ReactNode; time?: string }[]
- loading
The work is still going. The list stays open and grows. When it turns false, the list folds into one line.
boolean · default false
- total
How many steps there will be. The line counts them while loading, like Step 3 of 5.
number
- duration
How long it has run, or ran in all, like "34s".
string
- error
The work stopped on its last step. Say why in plain words. It shows under the line, and the list stays open.
ReactNode
- label
The line while loading. It pulses slowly.
ReactNode · default "Working"
- summary
The folded line once done. Default "Done in 34s, 5 steps", or "Failed at step 3 of 5, after 34s" with an error.
ReactNode
- defaultOpen
Starts open or folded. Default: open while loading or failed, folded when done.
boolean
- open
Controls whether it is open. Pair it with onOpenChange. Then it no longer folds itself.
boolean
- onOpenChange
Called when it opens or folds.
(open: boolean) => void
- className
Extra classes on the root. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| steps | { id?: string; label: ReactNode; detail?: ReactNode; time?: string }[] | - | Required. The steps so far, oldest first. While loading, the last one is under way. |
| loading | boolean | false | The work is still going. The list stays open and grows. When it turns false, the list folds into one line. |
| total | number | - | How many steps there will be. The line counts them while loading, like Step 3 of 5. |
| duration | string | - | How long it has run, or ran in all, like "34s". |
| error | ReactNode | - | The work stopped on its last step. Say why in plain words. It shows under the line, and the list stays open. |
| label | ReactNode | "Working" | The line while loading. It pulses slowly. |
| summary | ReactNode | - | The folded line once done. Default "Done in 34s, 5 steps", or "Failed at step 3 of 5, after 34s" with an error. |
| defaultOpen | boolean | - | Starts open or folded. Default: open while loading or failed, folded when done. |
| open | boolean | - | Controls whether it is open. Pair it with onOpenChange. Then it no longer folds itself. |
| onOpenChange | (open: boolean) => void | - | Called when it opens or folds. |
| className | string | - | Extra classes on the root. Later classes win. |
Accessibility
- The line is one button that opens and folds the steps, and it tells screen readers if it is open.
- The steps are an ordered list named Steps. The one under way says (working), and a failed one says (failed), to screen readers.
- Each new step is said politely in a hidden status region, and so is the summary when the work ends.
- A failure is shown three ways: the words Failed at, an icon, and the danger color. The reason is plain text under the line.
- The spinners and the pulse stop when the theme turns motion off, and for reduced motion. The list then opens and folds at once.
Examples
While running
<ProgressLog
loading
total={4}
duration="12s"
steps={[
{ id: "upload", label: "Uploaded contacts.csv", detail: "2,400 rows" },
{ id: "check", label: "Checking each row" },
]}
/>Done
<ProgressLog
duration="34s"
steps={[
{ id: "upload", label: "Uploaded contacts.csv" },
{ id: "check", label: "Checked 2,400 rows" },
{ id: "merge", label: "Merged 64 duplicates" },
]}
/>Failed
<ProgressLog
total={4}
duration="9s"
error="Row 214 has no email. Fix it and import again."
steps={[
{ id: "upload", label: "Uploaded contacts.csv" },
{ id: "check", label: "Checking each row" },
]}
/>