Skip to content

Tool Call

One thing the assistant did, such as searching or running a query: its name, where it is (waiting, running, needs approval, done, failed), how long it took, and on request what went in and out.

AIv0.1.0experimentalWCAG 2.2 AAView spec

Bring your own model.

These components only show what you give them, in one message shape, and report what the person does. They never call a model or a server, so they work with any backend. They are built for how assistive technology handles streaming text, tool steps and approvals.

Read every order from the last 12 months

Low risk

import { ToolCall, type ToolPart, type ToolState } from "@rdloom/react";

const states: ToolState[] = ["pending", "running", "awaiting-approval", "approved", "denied", "done", "failed"];

const tool = (state: ToolState): ToolPart => ({
  type: "tool",
  id: state,
  name: "query_sales",
  title: "Searching your sales data",
  state,
  startedAt: "2026-03-01T10:00:00Z",
  endedAt: state === "done" ? "2026-03-01T10:00:03Z" : undefined,
  error: state === "failed" ? "The database did not answer in time." : undefined,
  approval: state === "awaiting-approval" ? { summary: "Read every order from the last 12 months", risk: "low" } : undefined,
});

// Every state has its own icon and its own words: nothing depends on color alone.
export default function ToolCallStatesExample() {
  return (
    <div className="flex w-[30rem] max-w-full flex-col gap-2">
      {states.map((state) => (
        <ToolCall key={state} tool={tool(state)} />
      ))}
    </div>
  );
}

Installation

npx rdloom add tool-call

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/tool-call.json

Works, but without upgrade tracking.

Usage

import { ToolCall, type ToolPart, type ToolState } from "@rdloom/react";

<div className="flex w-[30rem] max-w-full flex-col gap-2">
  {states.map((state) => (
    <ToolCall key={state} tool={tool(state)} />
  ))}
</div>

With details

Input
{
  "from": "2026-02-01",
  "to": "2026-02-28",
  "groupBy": "week"
}
Result
{
  "rows": 4,
  "total": 48210
}
import { ToolCall } from "@rdloom/react";

export default function ToolCallWithDetailsExample() {
  return (
    <div className="w-[30rem] max-w-full">
      <ToolCall
        defaultExpanded
        tool={{
          type: "tool",
          id: "sales",
          name: "query_sales",
          title: "Searching your sales data",
          state: "done",
          startedAt: "2026-03-01T10:00:00Z",
          endedAt: "2026-03-01T10:00:02.400Z",
          input: { from: "2026-02-01", to: "2026-02-28", groupBy: "week" },
          output: { rows: 4, total: 48210 },
        }}
      />
    </div>
  );
}

Needs approval

Delete 12 draft invoices

High risk · Can't be undone

import { useState } from "react";
import { ToolCall, updateTool, type ChatMessage } from "@rdloom/react";

// You own the state. Approving or declining moves the tool forward; focus returns to the tool.
export default function ToolCallNeedsApprovalExample() {
  const [message, setMessage] = useState<ChatMessage>({
    id: "m",
    role: "assistant",
    parts: [
      {
        type: "tool",
        id: "t",
        name: "delete_drafts",
        title: "Delete draft invoices",
        state: "awaiting-approval",
        approval: { summary: "Delete 12 draft invoices", risk: "high", reversible: false },
      },
    ],
  });
  const tool = message.parts[0];
  return (
    <div className="w-[30rem] max-w-full">
      {tool.type === "tool" && (
        <ToolCall
          tool={tool}
          onApprove={(id) => setMessage((m) => updateTool(m, id, { state: "approved" }))}
          onDeny={(id) => setMessage((m) => updateTool(m, id, { state: "denied" }))}
        />
      )}
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
toolrequired

The tool part from the message.

ToolPartnone
onApprove

Called with the tool's id when the person approves it.

(toolId: string) => voidnone
onDeny

Called with the tool's id when the person declines it.

(toolId: string) => voidnone
defaultExpanded

Show the input and result at first.

booleanfalse
focusApproval

Move focus to the approval box when this tool asks for approval.

booleanfalse

Accessibility

Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Enter / Space on the header: shows or hides the input and result
  • Tab: moves through the header, the approval buttons and the details

Screen readers announce

  • The header reads as a button, e.g. "Searching your sales data, Running, collapsed"
  • A change is announced once: "Searching your sales data: Done"
  • A failed tool also reads its error message

What your code must do

  • State is said in words ("Running", "Done", "Needs your approval") and drawn with a different icon for each, never color alone
  • State changes are announced politely; a tool that arrives already finished is not read out again
  • The header is a real button with aria-expanded, and only when there is something to show
  • Input and result are scrollable regions that can be focused
  • Once an approval is answered, focus returns to the tool's header instead of being lost
  • Odd or huge values never throw: long output is cut with a count of what was left out

Guidelines

Use it when

  • Showing the steps an assistant took to get to an answer

Avoid it when

  • A progress bar for a long job: use Progress
  • Steps a person walks through: use Steps

Don't

  • Showing only a spinner with no words
  • Hiding an approval inside a collapsed section

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-surface-default
  • --rd-color-surface-subtle
  • --rd-color-border-default
  • --rd-color-border-strong
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-action-primary
  • --rd-color-feedback-success
  • --rd-color-feedback-danger
  • --rd-color-feedback-warning
  • --rd-color-feedback-info
  • --rd-color-focus-ring
  • --rd-radius-control