Marble UI
← Components

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>

PropTypeDefaultDescription
name *requiredstring—
status *required'running' | 'done' | 'failed' | 'waiting'—
args{}—
classNamestring—
defaultExpandedboolean—
durationMsnumber—
errorstring—
iconReactNode—
result{}—
titleReactNode—Human summary, e.g. "Searched the web for 'react aria sheet'".

<ToolCallGroup>

Compact row of several tool calls ("Used 3 tools").

PropTypeDefaultDescription
children *requiredReactNode—
labelstring—

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>
  • status is running, done, failed or waiting. Each has its own icon shape and spoken label (“Needs input” for waiting), but waiting has no approve or deny buttons; show an ApprovalCard for that.
  • Without title the header reads “Called name”; with one, name isn’t shown.
  • args and result take a string (shown as is) or a JSON value (pretty-printed); only the result gets a copy button. error replaces the result.
  • Collapsed by default (defaultExpanded opens it). ToolCallGroup only adds a text label; it doesn’t collapse.
Example source · show
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