Tool Call Timeline React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React tool call timeline for AI agents: each step with its status, time, input and output, plus approval and retry when a call needs them.
Each call is a row: a round marker says its status (a dashed ring while queued, a spinner while it runs, a check that pops once when it finishes, a cross when it fails, a hand that pings while it waits for a person), then a human title, the tool's id in a small mono chip and how long it took. A running call's title shimmers and its time ticks in tenths of a second. Click or tap a row, or press Enter or Space on it, to open its input and output as highlighted JSON (text results wrap), its error and its logs; long blocks scroll inside themselves and every block has a copy button. A call that waits for approval shows Reject and Approve; a failed one opens by itself and shows Retry; a button whose handler returns a promise spins until it settles, and focus moves back to the row when the buttons go away. Sub-steps sit indented under their step. The card variant adds a header with the run's state (a spinner, or Needs approval, Failed, Done in words; a hand or an alert in its icon tile when a person is needed or a call failed, which alone carries it in a card narrower than 448px), its progress while it runs (2 of 5 done) and total time, and a button that opens or closes every row at once; the inline variant is a one-line summary (Used 4 tools, 2.8 s) that opens by itself while a call runs or waits for approval and closes again 1.2 s after everything finished, unless you opened it yourself or focus is inside it (you just approved a call), so focus never drops to the page. Status changes are announced once each to screen readers. Rows lay out by their own width, so a timeline in a chat column or a side panel fits: when a row is narrow the tool id moves under the title and Approve and Reject share the width; on a phone they are 44px tall, rows have 44px targets and code blocks scroll sideways inside themselves. Reduced motion swaps the springs for quick fades and stops the shimmer and the ping.
Tool Call Timeline is a Pro React component in the React AI components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add tool-call-timeline, together with Badge, Button, Code Block, Copy Button, Spinner and 10 more and the npm packages motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add tool-call-timelineTool Call Timeline is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import ToolCallTimeline from @/components/ui/tool-call-timeline.
import { ToolCallTimeline, type ToolCall } from "@/components/ui/tool-call-timeline"
export function AgentRun({
calls,
onApprove,
onReject,
}: {
calls: ToolCall[]
onApprove: (call: ToolCall) => Promise<void>
onReject: (call: ToolCall) => void
}) {
return (
<ToolCallTimeline
variant="card"
title="Collections agent"
calls={calls}
onApprove={onApprove}
onReject={onReject}
locale="en-US"
/>
)
}
// a call waiting for a person, as the model sent it
export const sendReminder: ToolCall = {
id: "call-send",
name: "send_email",
title: "Sending the email",
status: "awaiting-approval",
input: { to: "adam.foster@techsolutions.com", subject: "Reminder for invoice #2026-0405" },
}Props
calls*ToolCall[]The calls in order, in the ToolCall shape from lib/ai-chat: id, name, title, status, input, output, error, startedAt, endedAt, durationMs, logs and steps (sub-steps). Keep them immutable so only the call that changed re-renders.
variantdefault"timeline""timeline" | "card" | "inline"timeline: markers on a rail, for an agent page. card: the timeline in a panel with a header (title, progress, total time and the run's state). inline: a one-line summary that opens into the timeline, for a chat message.
titledefault""ReactNodeThe card's heading or the inline summary's text. Empty falls back to labels.summary (Used 4 tools). The card's second line shows the progress while the run goes on (2 of 5 done), then the summary, with the total time.
sizedefault"md""sm" | "md"Titles 13 / 14px, rows 32 / 40px, markers 20 / 24px. Sub-steps always use sm. On touch screens md rows grow to 44px and sm rows keep their look with a 44px hit area.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Paints the Approve button, the card header's icon, the inline summary's spinner and the focus tint. Markers always use the status colors (running is info, done success, failed danger, waiting warning).
colordefault""stringAny CSS color; replaces the tone. Text on the Approve button turns black or white by lightness.
codeAccentdefault""stringA hex color the input and output blocks build their syntax colors from (the code block's accent). Empty keeps the code block's own.
showToolNamedefaulttruebooleanThe tool's id (search_invoices) in a mono chip next to the title; in a row narrower than 512px it sits under the title (sm rows hide it below 448px).
showDurationsdefaulttruebooleanHow long each call took (live while it runs, from startedAt) and the run's total in the card and inline summaries.
showInputdefaulttruebooleanThe arguments the model sent, in an opened row.
showOutputdefaulttruebooleanWhat the tool returned, in an opened row.
codeMaxHeightdefault240numberHeight in px an input or output block may reach before it scrolls inside itself. Copy always takes the whole value.
defaultOpendefaultfalsebooleaninline: start with the timeline open under the summary.
openbooleaninline: controlled open state. autoOpen asks for changes through onOpenChange.
onOpenChange(open: boolean) => voidinline: called when the summary is pressed and when autoOpen opens or closes it.
autoOpendefaulttruebooleaninline: opens while a call runs or waits for approval and closes 1.2 s after everything finished, unless the person opened it themselves or focus is inside it.
expandErrorsdefaulttruebooleanA failed call starts with its details open, and opens by itself when a running call fails.
expandedKeysstring[]Controlled: the ids of the calls (and sub-steps) whose details are open.
defaultExpandedKeysstring[]The calls whose details start open (failed calls are added when expandErrors is on).
onExpandedKeysChange(keys: string[]) => voidCalled with the next ids when a row is opened or closed, and when a call fails with expandErrors on.
onApprove(call: ToolCall) => (void | Promise<unknown>)Shows Approve on calls awaiting approval. Return a promise to keep the button spinning (and Reject disabled) until it settles; then set the call's status.
onReject(call: ToolCall) => (void | Promise<unknown>)Shows Reject next to Approve. Set the call to canceled, or back to the model, in the handler.
onRetry(call: ToolCall) => (void | Promise<unknown>)Shows Retry on failed calls, spinning while a returned promise is pending.
renderInput(call: ToolCall) => ReactNodeReplaces the JSON view of a call's input (a map, a diff). Return undefined to keep the JSON, null to hide it.
renderOutput(call: ToolCall) => ReactNodeReplaces the JSON view of a call's output, e.g. invoices as a small table. Return undefined to keep the JSON, null to hide it.
localedefault""stringLocale for durations (0.8 s in en-US, 0,8 s in de-DE) and counts. Empty uses UIProvider's, then the page's.
nowDate | numberA fixed clock for running durations, for screenshots and tests. Without it running calls tick on the shared ticker (lib/ai-chat).
emptyStateReactNodeReplaces the No steps yet row shown while calls is empty.
labelsPartial<ToolCallTimelineLabels>Every visible and spoken string: summary(count, formatted), progress(done, total, formattedDone, formattedTotal), running(title), input, output, error, logs, approve, reject, retry, showDetails, hideDetails, empty, status (one word per status, partial), announce(title, status), copy, copied. English defaults in DEFAULT_TOOL_CALL_TIMELINE_LABELS.
classNamesPartial<Record<"summary" | "summaryIcon" | "summaryTitle" | "summaryMeta" | "list" | "item" | "rail" | "marker" | "header" | "title" | "name" | "duration" | "status" | "chevron" | "details" | "description" | "input" | "output" | "error" | "logs" | "approval" | "retry" | "steps" | "empty", string>>Extra classes for the inner parts; summary is the card header or the inline summary button.
classNamestringExtra classes for the root, merged last.
Parts
ToolCallItem
componentOne call as an <li> with its details, approval and sub-steps, for lists of your own: render it inside an <ol> and set last on the final one to end the rail. Props: { call: ToolCall; last?: boolean; expanded?: boolean; defaultExpanded?: boolean; onExpandedChange?; expandErrors?; showInput?; showOutput?; showToolName?; showDurations?; renderInput?; renderOutput?; codeMaxHeight?; codeAccent?; onApprove?; onReject?; onRetry?; size?; tone?; color?; now?; locale?; labels?; classNames? }.
ToolCallStatusMarker
componentThe 20 / 24px round status marker on its own (agent steps, workflow node runs); decorative, so pair it with the status in words. Props: { status: ToolCallStatus; size?: "sm" | "md" }.
DEFAULT_TOOL_CALL_TIMELINE_LABELS
constantThe English strings, to spread and override. Type: ToolCallTimelineLabels.
Examples
Inline in a message
A one-line summary above the answer that opens while tools run and folds away 1.2 s after.
Failure and retry
The tax portal answers 503: the call opens with its error and logs, and Retry succeeds on the second try.
Approval in brand
A deal the agent wants to create waits for a person; Approve spins until its promise settles.
Nested steps
A report step that ran three sub-steps, replayed live.
Custom output
renderOutput turns the invoices into rows with dollar amounts.
Narrow side panel
size sm in a 320px card, with a pinned clock for a stable running time.
Queued plan in English
Default labels, color="#7C3AED" and pending steps that run one by one.
Dependencies
Files
- components/ui/tool-call-timeline.tsx
FAQ
Is the React Tool Call Timeline component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Tool Call Timeline?
Run npx wingo-ui@latest add tool-call-timeline. It also adds the Wingo UI items it builds on (Badge, Button, Code Block, Copy Button, Spinner and 10 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/tool-call-timeline.
How does Tool Call Timeline behave on phones?
Rows lay out by their own width, so a timeline in a chat column or a side panel fits: when a row is narrow the tool id moves under the title and Approve and Reject share the width; on a phone they are 44px tall, rows have 44px targets and code blocks scroll sideways inside themselves.
See also
Used in
More React AI components
Articles using this component
- AI Agent Tool Call UI: Steps, Approvals and Errors
Building UI with AI agents11 min read
- AI Elements Alternative: 5 React AI Chat Kits Compared
Building UI with AI agents11 min read