AI Plan Steps
A React component for agent plan tracking, driven purely by data — steps flow through pending → running → done / failed / skipped with tool-icon chips, nested sub-steps joined by a connector line, a breathing pulse on the active step, a completion ratio and total elapsed time; status changes swap icons via AnimatePresence and new steps slide in from below.
The todo tracker you see in agent products: hand it a steps array and each row renders by status — an empty circle for pending, a spinning ring (with the whole row breathing) for running, a check that springs in for done, an X for failed, a dashed circle for skipped. Next to the title sit a tool chip (tool names map to built-in lucide icons, overridable via toolIcons) and the elapsed time; rows with a detail or sub-steps expand, and sub-steps are indented and joined by a connector line on the left. The header shows the completion ratio x/y, a thin three-segment progress bar (done / failed / skipped) and the total elapsed time. The component runs no timers of its own — every state comes from your data, so whatever the agent reports is what gets drawn; status changes swap icons with AnimatePresence, and newly added steps slide in from below.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add ai-plan-steps.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<AiPlanSteps/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/ai-plan-steps.en.jsonOr, once registries are configured in components.json, install it as @webberui/ai-plan-steps.
Usage
import { AiPlanSteps, type PlanStep } from "@/components/ui/ai-plan-steps";
const steps: PlanStep[] = [
{
id: "audit",
title: "Audit the current login flow",
status: "done",
tool: "read",
durationMs: 1050,
children: [
{ id: "audit-read", title: "Read the auth/ directory", status: "done", tool: "read", durationMs: 640 },
{ id: "audit-grep", title: "Flag duplicated validation logic", status: "done", tool: "grep", durationMs: 410 },
],
},
{ id: "extract", title: "Extract a shared validateCredentials()", status: "running", tool: "edit" },
{ id: "test", title: "Run unit tests", status: "pending", tool: "test" },
];
<AiPlanSteps
steps={steps}
title="Plan"
onStepClick={(step) => console.log(step.id)}
/>Progress is driven by you updating the steps array (for example, one setState per agent report); the component only turns the difference into animation.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
steps | PlanStep[] | — | Step data (with nested children); every state transition is driven by this data |
title | string | "Task Planning" (Plan) | Header title text; an empty string hides the title |
toolIcons | Record<string, PlanToolIcon> | — | Tool name → icon map, overriding built-in entries with the same key (use lowercase keys) |
showProgress | boolean | true | Show the completion ratio (x/y), thin progress bar and total elapsed time in the header |
collapsible | boolean | true | Whether rows with a detail or sub-steps can be toggled; false keeps them always expanded with no chevron |
defaultCollapsed | boolean | false | In uncontrolled mode, whether collapsible rows start collapsed |
expandedIds | string[] | — | Controlled list of expanded step ids; when provided the expansion state is fully managed outside |
onExpandedChange | (ids: string[]) => void | — | Callback when the expanded list changes; receives every expanded row id after the change |
onStepClick | (step: PlanStep) => void | — | Callback when any row is clicked (independent of toggling) |
compact | boolean | false | Compact mode: smaller type, spacing and icons; the tool chip keeps only its icon |
strikeDone | boolean | false | Strike through the titles of done steps |
labels | Partial<AiPlanStepsLabels> | — | Override UI strings (status text for screen readers, ratio wording, empty state, etc.) |
className | string | — | Forwarded to the outermost container |
PlanStep
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique id used for the React key, expansion state and aria-controls; stick to alphanumerics, - and _ |
title | string | — | Step title |
status | "pending" | "running" | "done" | "failed" | "skipped" | — | Current status |
tool | string | — | Tool name (e.g. read, bash); mapped to an icon via toolIcons and shown as a chip |
detail | string | — | Extra text shown when expanded (result summary, error message, etc.); rendered in red for failed steps |
durationMs | number | — | Elapsed time in milliseconds; when set it is shown at the end of the row and counted in the total |
children | PlanStep[] | — | Sub-steps; rows with sub-steps or a detail get an expand chevron |
AiPlanStepsLabels
| Field | Type | Default | Description |
|---|---|---|---|
pending | string | "Pending" (Pending) | Screen-reader text for the pending status |
running | string | "Running" (Running) | Screen-reader text for the running status, also used in the live region as "Running: …" |
done | string | "Done" (Done) | Screen-reader text for the done status |
failed | string | "Failed" (Failed) | Screen-reader text for the failed status |
skipped | string | "Skipped" (Skipped) | Screen-reader text for the skipped status |
progress | string | "Completed" (completed) | Wording after the completion ratio numbers |
totalDuration | string | "Total Time" (Total time) | Accessible label for the total elapsed time |
empty | string | "No Steps Yet" (No steps yet) | Text shown when there are no steps |
summarizePlanSteps(steps) (returns per-status counts over the flattened tree plus the total elapsed time), formatPlanDuration(ms) (820ms / 1.4s / 12s / 1m 05s), and the PlanStep, PlanStepStatus, PlanToolIcon, PlanSummary, AiPlanStepsLabels and AiPlanStepsProps types are also named exports.
How it works
- Built-in tool icon map:
read,write,edit,search,grep,glob,bash,shell,terminal,web,fetch,browse,db,database,sql,git,test,build,deploy,code,think,plan. Lookup first tries the whole lowercased name, then falls back to a "contains keyword" match (read_file→read,web_search→search, longer keys win); anything unmatched gets a wrench icon. Pass your own map viatoolIconsto override or extend it - Ratio and total time: counts are computed over the flattened tree (sub-steps at every level included); for total time, a parent with its own
durationMswins, otherwise its children are summed, so parent and children are never double-counted - Expansion state: uncontrolled mode only records rows the user has toggled and infers the rest from
defaultCollapsed, so steps added later follow the same default; useexpandedIds+onExpandedChangewhen you need to persist or sync it - Animation: status icons swap through
AnimatePresencepopLayout (the done check springs in), the running row breathes with a faint blue overlay, progress bar widths ease, new rows slide in from below with a layout position animation and removed rows fade out while the rest close the gap; the component itself has no setTimeout / setInterval
Accessibility
- The root is a
sectionlabelled by the title viaaria-labelledby; lists userole="list"/role="listitem"(Safari drops list semantics forlist-style: none, so the explicit roles keep them), and the running row carriesaria-current="step" - Collapsible rows are native
buttons (type="button") witharia-expandedandaria-controlspointing at the detail region; Tab / Enter / Space work from the keyboard andfocus-visibleshows a ring - Each title is followed by screen-reader-only status text ("(Running)" etc., translatable via
labels); the header also has arole="status"aria-live="polite"summary that is announced whenever the ratio or the running steps change - The progress bar is a
role="progressbar"witharia-valuenowset to the done count andaria-valuemaxto the total number of steps - When the user has "reduce motion" enabled at the system level, the spin, breathing, spring-in, slide-in and layout-shift animations are disabled and status changes keep only fades and color changes
AI Diff Suggestion Card
AI suggestions presented as an inline diff — deletions struck through in red, additions in green, accepted or rejected one by one.
AI Model Arena
An arena-style side-by-side blind test React component: two anonymous responses stream in sync, you vote A / B / tie / both bad, then the cards flip to reveal the model names and provider color blocks, crown the winner, and roll the scoreboard numbers.