Pro

Tool steps

Pro component

An agent’s actions as a quiet timeline. Each step opens to its raw input and output, and the list folds to “Ran 5 steps in 12s” when the run ends.

Updating the shopping list for Saturday supper

  1. Not started:

    Saturday supper, 2 pages, 18 items

    Input
    read_file({ "path": "events/saturday-supper/shopping-list.pdf" })
  2. Not started:

    Lamb shoulder, 2.8 kg, delivery to 14 Alder Lane

    Input
    search_prices({
      "item": "lamb shoulder, bone-in",
      "stores": ["market.example.com", "grocer.example.org", "butcher.example.com"]
    })
  3. Not started:

    Ticketed dinners over 10 guests need a permit

    Input
    fetch("https://county.example.org/health/fees.json")

Skipped and cancelled

  1. Done: 0.9s

    policies/cancellations.md

    Input
    read_file({ "path": "policies/cancellations.md" })
    Output
    Cancelled 48 hours ahead: full refund. Later: half refund.
  2. Done: Looked up Ben’s booking1.4s

    2 seats, cancelled 3 days ahead

  3. Skipped: Ask Ben for a reasonSkipped

    Ben already explained in his message

  4. Cancelled: Issue the refundCancelled

    Stopped by you before sending

Installation

This is a Pro component. UI Polish Pro costs $149 one time and includes every Pro component, block and template, now and later. Add your license key to your MCP connection or components.json, then install it either way below.

With UI Polish connected, ask your agent:

Add the UI Polish tool steps to this project.

Or with the shadcn CLI:

npx shadcn@latest add @uipolish/tool-steps
Brings inButton, Icons, Text morph, Text

What an agent did, as a vertical list of steps joined by a hairline. Each step has a status icon in a fixed box, a title, an optional detail line and a time. Steps with raw input and output open to show them in mono. A summary line on top reads "Running 3 of 5…" while the agent works and "Ran 5 steps in 12s" when it's done, and folds the list away.

When to use it

  • An agent that calls tools, reads files, searches or edits on the user's behalf, and the user should be able to see and check each action.
  • Runs that take long enough for progress to matter: the running step's timer and the summary count show where it is.
  • Any run where a step can fail and be retried without starting over.

When not to

  • The model's private reasoning: use the reasoning block.
  • A single action with one result ("Saving…", "Saved"): a button's busy state or a toast says it with less.
  • A checklist the user works through themselves. These steps are things that happened, not tasks.

States

Per step:

StatusIconTitleRight side
pendingHollow circle, subtleSubtlenothing
runningThe hollow circle with a dot breathing inside itText colorLive timer, whole seconds
doneFilled circle with a checkText colorTime taken, "2.4s"
failedDanger circle with "!"Text color, error message below in danger, Retry buttonTime taken
skippedCircle with a dash, subtleSubtle"Skipped"
cancelledCircle with a dash, subtleSubtle"Cancelled"

The icons are stacked in one 16px box and crossfade, so a status change never moves the title.

Steps with input or output have a title that's a button: it opens the input and output in a mono block capped at 180px that scrolls on its own. Output appears once the step settles.

The summary:

SituationLabel
Nothing started"5 steps queued"
Working, or between two steps"Running 3 of 5…" with a slow light band
A step failed and nothing is running"Step 3 of 5 failed" (the list opens if it was folded)
Every step done or skipped"Ran 5 steps in 12s" (the list folds)
Some steps didn't run"Ran 2 of 4 steps in 2s"

Timing. Give each step startedAt and finishedAt as Date.now() values from your agent's events. Running steps count up from startedAt; the summary's total runs from the first start to the last finish. Without them, the times are left out rather than guessed.

Titles. Write them as the action in the past tense once done ("Read menu.pdf", "Updated 4 recipes"), and in the present while running ("Reading menu.pdf"). The change morphs in place. Keep titles short, under about 40 characters; put the rest in detail.

Errors. Say what happened and, if you can, why: "market.example.org didn't respond within 10 seconds", not "Tool error". Retry calls onRetry(id); set that step back to running and the error clears.

Motion

WhatDurationEasing
Step appears: fade and 4px rise--ui-dur-fast (160ms)--ui-ease-enter
Status icon crossfade: old out, new in from scale 0.97out --ui-dur-instant (120ms), in --ui-dur-fast (160ms), scale --ui-dur-snap--ui-ease-color, --ui-ease-enter, --ui-ease-snap
Title and summary label changes--ui-dur-move (380ms)--ui-ease-move, via text morph
Light band across the summary--ui-dur-sweep (2.4s) per sweep, only while runninglinear
List or input/output opens--ui-dur-panel (280ms) height, --ui-dur-fast fade 75ms later--ui-ease-expand, --ui-ease-enter
List or input/output closes--ui-dur-base (200ms) height, --ui-dur-instant fade--ui-ease-collapse, --ui-ease-exit
Done check draws on, left to right, 60ms after the circle--ui-dur-base (200ms)--ui-ease-draw
Chevron turns a quarter turn, right to down--ui-dur-base (200ms)--ui-ease-glide
Running dot grows and shrinks--ui-dur-pulse (700ms) each way--ui-ease-pulse

Height animates through grid-template-rows: 0fr → 1fr. The list folds on its own only once, when the run finishes cleanly.

Under prefers-reduced-motion: reduce steps appear without rising, the band is replaced by static muted text, the done check appears already drawn, labels swap without morphing, panels open instantly, and the running dot holds its size and fades slowly instead.

Accessibility

  • The steps are an <ol> with an accessible name (label), so screen readers announce "list, 5 items" and each item's position.
  • Each step starts with its status in visually hidden text ("Done:", "Failed:"), so the icon is never the only signal.
  • A polite role="status" region announces each step once as it settles ("Read menu.pdf: done", "Checking wine prices failed: …") and the summary when the run ends. Timers are never announced.
  • The summary and expandable titles are buttons with aria-expanded and aria-controls. Folded content is inert.
  • Pressing Retry moves focus to that step's title, because the Retry button disappears once the step is running again.
  • Input and output blocks are focusable, labelled ("Input for Read menu.pdf") and scroll with the arrow keys.
  • Expandable titles get a 40px tall hit area on touch screens.

Before / after

Before: a spinner and a line of text that's replaced by each new action, with no record of what happened and nothing to do when it fails.

TSX
{busy && <p><Spinner /> {currentAction}…</p>}{error && <p className="error">Something went wrong</p>}

After: every action stays visible with its status and time, a failure says why and can be retried, and the list folds into one line when it's over.

TSX
<ToolSteps  label="Steps for the 14 Alder Lane dinner"  steps={[    { id: "read", title: "Read menu.pdf", status: "done", detail: "3 pages, 18 dishes",      startedAt: t0, finishedAt: t0 + 1600, input: readCall, output: readResult },    { id: "search", title: "Searching 3 suppliers", status: "running", startedAt: t0 + 1850 },    { id: "update", title: "Update shopping list", status: "pending" },  ]}  onRetry={(id) => agent.retry(id)}/>