Tool call
AIOne tool invocation by a model: name, a short summary of its arguments and its status, with the input and output inside the same container.
npx prodular-ui add tool-callStatuses
Done with its duration, running with a shimmer, queued, failed and waiting for approval.
Input
{
"vendor": "Northbeam Logistics",
"status": "unpaid",
"limit": 20
}Output
{
"count": 2,
"invoices": [
{
"id": "INV-20931",
"amount": 18240.5,
"due": "2026-10-30"
},
{
"id": "INV-20977",
"amount": 9612,
"due": "2026-11-14"
}
]
}import { DatabaseIcon, FileSearchIcon, MailIcon, ReceiptIcon, SendIcon } from "lucide-react"
import { ToolCall, ToolCallContent, ToolCallHeader, ToolCallIcon, ToolCallSection, ToolCallStatus, ToolCallSummary, ToolCallTitle,} from "@/components/ui/tool-call"
export default function ToolCallStatuses() { return ( <div className="flex w-full max-w-xl flex-col gap-2"> <ToolCall status="done" defaultOpen> <ToolCallHeader> <ToolCallIcon> <ReceiptIcon /> </ToolCallIcon> <ToolCallTitle>Search invoices</ToolCallTitle> <ToolCallSummary>vendor: Northbeam, status: unpaid</ToolCallSummary> <ToolCallStatus duration="0.9s" /> </ToolCallHeader> <ToolCallContent> <ToolCallSection label="Input" value={{ vendor: "Northbeam Logistics", status: "unpaid", limit: 20 }} /> <ToolCallSection label="Output" value={{ count: 2, invoices: [ { id: "INV-20931", amount: 18240.5, due: "2026-10-30" }, { id: "INV-20977", amount: 9612, due: "2026-11-14" }, ], }} /> </ToolCallContent> </ToolCall> <ToolCall status="running"> <ToolCallHeader> <ToolCallIcon> <DatabaseIcon /> </ToolCallIcon> <ToolCallTitle>Query general ledger</ToolCallTitle> <ToolCallSummary>accounts 6000 to 6999, October</ToolCallSummary> <ToolCallStatus /> </ToolCallHeader> </ToolCall> <ToolCall status="pending"> <ToolCallHeader> <ToolCallIcon> <FileSearchIcon /> </ToolCallIcon> <ToolCallTitle>Read contract</ToolCallTitle> <ToolCallSummary>Northbeam MSA 2026.pdf</ToolCallSummary> <ToolCallStatus /> </ToolCallHeader> </ToolCall> <ToolCall status="error"> <ToolCallHeader> <ToolCallIcon> <MailIcon /> </ToolCallIcon> <ToolCallTitle>Fetch remittance emails</ToolCallTitle> <ToolCallSummary>ap@ledgerline.com, last 7 days</ToolCallSummary> <ToolCallStatus /> </ToolCallHeader> <ToolCallContent> <ToolCallSection label="Error" value="403 Forbidden: the mailbox connection expired on Oct 18. Reconnect it in Settings, Integrations." /> </ToolCallContent> </ToolCall> <ToolCall status="approval"> <ToolCallHeader> <ToolCallIcon> <SendIcon /> </ToolCallIcon> <ToolCallTitle>Send payment reminder</ToolCallTitle> <ToolCallSummary>to billing@halcyonhealth.com</ToolCallSummary> <ToolCallStatus /> </ToolCallHeader> </ToolCall> </div> )}Approval
Actions with side effects wait for a person. The buttons sit under a divider in the same container.
Input
{
"invoice": "INV-20931",
"amount": 18240.5,
"currency": "USD",
"method": "ACH",
"pay_on": "2026-10-29",
"from_account": "Operating ****4410"
}"use client"
import * as React from "react"import { BanknoteIcon } from "lucide-react"
import { Button } from "@/components/ui/button"import { ToolCall, ToolCallApproval, ToolCallContent, ToolCallHeader, ToolCallIcon, ToolCallSection, ToolCallStatus, ToolCallSummary, ToolCallTitle, type ToolCallState,} from "@/components/ui/tool-call"
export default function ToolCallApprovalDemo() { const [status, setStatus] = React.useState<ToolCallState>("approval") const [denied, setDenied] = React.useState(false)
return ( <div className="flex w-full max-w-xl flex-col gap-3"> <ToolCall status={status} defaultOpen> <ToolCallHeader> <ToolCallIcon> <BanknoteIcon /> </ToolCallIcon> <ToolCallTitle>Schedule payment</ToolCallTitle> <ToolCallSummary>$18,240.50 to Northbeam Logistics</ToolCallSummary> <ToolCallStatus duration="1.1s">{denied ? "Denied" : undefined}</ToolCallStatus> </ToolCallHeader> <ToolCallContent> <ToolCallSection label="Input" value={{ invoice: "INV-20931", amount: 18240.5, currency: "USD", method: "ACH", pay_on: "2026-10-29", from_account: "Operating ****4410", }} /> </ToolCallContent> {status === "approval" && ( <ToolCallApproval> Copilot wants to schedule a payment. It runs on Oct 29. <Button variant="ghost" size="sm" onClick={() => { setDenied(true) setStatus("error") }} > Deny </Button> <Button size="sm" onClick={() => { setStatus("running") setTimeout(() => setStatus("done"), 1400) }} > Approve payment </Button> </ToolCallApproval> )} </ToolCall> {status !== "approval" && ( <Button variant="outline" size="sm" className="self-start" onClick={() => { setDenied(false) setStatus("approval") }} > Reset </Button> )} </div> )}Anatomy
<ToolCall status="done"> <ToolCallHeader> <ToolCallIcon><ReceiptIcon /></ToolCallIcon> <ToolCallTitle>Search invoices</ToolCallTitle> <ToolCallSummary>vendor: Northbeam</ToolCallSummary> <ToolCallStatus duration="0.9s" /> </ToolCallHeader> <ToolCallContent> <ToolCallSection label="Input" value={input} /> <ToolCallSection label="Output" value={output} /> </ToolCallContent></ToolCall>Guidance
Use it when
- For every tool the model calls, in the order it called them, between Reasoning and Response.
- Approval for anything that writes, sends, pays or deletes.
Avoid
- Showing raw tool names like search_invoices_v2. Write a human title: Search invoices.
- Opening every call by default. Keep them collapsed unless one failed or needs approval.
- Nesting code blocks or cards inside. ToolCallSection already separates input and output with dividers.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Always pass status, and update it as the call progresses: pending, running, then done or error. Use approval when a person must allow the call before it runs.
- Write ToolCallTitle as verb plus object (Search invoices, Schedule payment) and ToolCallSummary as the two or three arguments that matter, not the full JSON.
- Pass duration to ToolCallStatus when status is done.
- Put objects in ToolCallSection value. It formats JSON and adds a copy button. Use label Input, Output or Error.
- For approval, render ToolCallApproval with one sentence that says what will happen, a ghost Deny button and a default button that names the action. Never run the tool before approval.
- Render a failed call with status error and an Error section, and let the model explain the failure in its Response.
API
ToolCall
| Prop | Type | Default | Description |
|---|---|---|---|
| status | "pending" | "running" | "done" | "error" | "approval" | "pending" | Drives the icon, shimmer, label and border. |
| open / defaultOpen | boolean | Whether input and output are visible. |
ToolCallStatus
| Prop | Type | Default | Description |
|---|---|---|---|
| duration | string | Replaces Done once finished, for example 1.4s. |
ToolCallSection
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | Input, Output or Error. | |
| value | unknown | Objects render as formatted JSON, strings as they are. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add tool-call# or, with the registry in components.jsonnpx shadcn@latest add @prodular/tool-call