Tool Call Card
Preview: States
One tool invocation: name, status, timing; expands to show arguments and result.
Import
import { ToolCall, ToolCallGroup } from '@marbleui/ui'Props
<ToolCall>
| Prop | Type | Default | Description |
|---|---|---|---|
| name *required | string | — | |
| status *required | 'running' | 'done' | 'failed' | 'waiting' | — | |
| args | {} | — | |
| className | string | — | |
| defaultExpanded | boolean | — | |
| durationMs | number | — | |
| error | string | — | |
| icon | ReactNode | — | |
| result | {} | — | |
| title | ReactNode | — | Human summary, e.g. "Searched the web for 'react aria sheet'". |
<ToolCallGroup>
Compact row of several tool calls ("Used 3 tools").
| Prop | Type | Default | Description |
|---|---|---|---|
| children *required | ReactNode | — | |
| label | string | — |
Accessibility
Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria Button · React Aria Disclosure · React Aria DisclosurePanel · React Aria Heading. Follow the primitive's docs for the full keyboard map.
Usage notes
One tool invocation in an agent reply: status, summary and timing, expanding to show arguments and result. Place them in the assistant ChatMessage before the MarkdownResponse, inside a ToolCallGroup when there are several.
<ToolCallGroup label="Used 2 tools · 1.3s">
<ToolCall name="search_web" title="Searched the web for “SaaS churn benchmarks”" status="done" durationMs={1240} args={{ query: 'SaaS churn benchmarks' }} result={hits} />
<ToolCall name="run_query" status="running" args={{ sql: 'select account, pct_change from usage_trend' }} />
</ToolCallGroup>
statusisrunning,done,failedorwaiting. Each has its own icon shape and spoken label (“Needs input” forwaiting), butwaitinghas no approve or deny buttons; show anApprovalCardfor that.- Without
titlethe header reads “Calledname”; with one,nameisn’t shown. argsandresulttake a string (shown as is) or a JSON value (pretty-printed); only the result gets a copy button.errorreplaces the result.- Collapsed by default (
defaultExpandedopens it).ToolCallGrouponly adds a text label; it doesn’t collapse.
Example source · show· hide
import { ToolCall, ToolCallGroup } from './tool-call'
const I = ({ d }: { d: string }) => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden><path d={d} /></svg>
)
export const styles = {
States: () => (
<ToolCallGroup label="Used 3 tools · 5.1s">
<div className="flex w-[30rem] flex-col gap-1.5">
<ToolCall name="search_web" title="Searched the web for “react aria sheet snap points”" status="done" durationMs={1240} icon={<I d="m21 21-4.3-4.3M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16" />} args={{ query: 'react aria sheet snap points', limit: 5 }} result={[{ title: 'Sheet – React Aria', url: 'https://react-aria.adobe.com/Sheet' }]} />
<ToolCall name="read_file" title="Read drawer.tsx" status="done" durationMs={84} icon={<I d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7zM14 2v5h6" />} args={{ path: 'packages/ui/src/drawer/drawer.tsx' }} result="export function Drawer(…)" />
<ToolCall name="run_tests" status="running" icon={<I d="M5 3l14 9-14 9z" />} args={{ filter: 'drawer' }} />
</div>
</ToolCallGroup>
),
Error: () => (
<ToolCall className="w-[30rem]" name="send_email" title="Tried to send the campaign email" status="failed" durationMs={3200} defaultExpanded args={{ to: 'newsletter@acme.com', template: 'oct-update' }} error="SMTP 550: sender domain not verified" />
),
}Source: packages/ui/src/tool-call/tool-call.tsx
Catalog
- Category
- ai-agents
- Priority
- Pro
- Primitive
- composed
- React Aria
- Disclosure
- Base UI
- Collapsible