ToolCall

free

ToolCall exposes a tool invocation with running, completed, and failed states plus formatted input and output details.

motionreact-iconsshiki
Preview
Dark
import ToolCall from '@/components/composites/ToolCall';

export default function UsageDemo() {
  return (
    <div className="w-full max-w-xl">
      <ToolCall
        name="getWeather"
        state="output-available"
        input={{ city: 'Singapore' }}
        output={{ temperature: 31, unit: 'C', condition: 'Cloudy' }}
      />
    </div>
  );
}

States

Preview
Dark

Input

{
  "city": "Singapore"
}

Error

The weather provider did not return a result.
import ToolCall from '@/components/composites/ToolCall';

export default function StatesDemo() {
  return (
    <div className="w-full max-w-xl space-y-3">
      <ToolCall
        name="getWeather"
        state="input-streaming"
        input={{ city: 'Singapore' }}
      />
      <ToolCall
        name="getWeather"
        state="input-available"
        input={{ city: 'Singapore' }}
      />
      <ToolCall
        name="getWeather"
        state="output-error"
        input={{ city: 'Singapore' }}
        errorText="The weather provider did not return a result."
      />
    </div>
  );
}

Custom Result

Pass children when the tool output needs a product-specific renderer. This example treats the output as a completed web search: a nested SearchResult disclosure owns the query header, result count, collapse affordance, and source rows instead of the default JSON block.

Preview
Dark
import { PiCaretDown, PiSparkle } from 'react-icons/pi'
import Collapsible from '@/components/ui/Collapsible';
import ToolCall from '@/components/composites/ToolCall';

const results = [
  {
    title: 'Extended thinking - Claude Platform Docs',
    domain: 'platform.claude.com',
    url: 'https://platform.claude.com/docs/en/build-with-claude/extended-thinking',
    favicon: '/img/thumbs/brands/claude.png',
  },
  {
    title: 'Building with Claude Extended Thinking | by Cobus Greyling | Medium',
    domain: 'medium.com',
    url: 'https://cobusgreyling.medium.com/building-with-claude-extended-thinking-d1a8b3130834',
    favicon: '/img/thumbs/brands/medium.png',
  },
  {
    title: 'Claude on X',
    domain: 'x.com',
    url: 'https://x.com/claudeai',
    favicon: '/img/thumbs/brands/x.png',
  },
  {
    title: 'Extended thinking examples - Anthropic Cookbook',
    domain: 'github.com',
    url: 'https://github.com/anthropics/anthropic-cookbook/',
    favicon: '/img/thumbs/brands/github.png',
  },
];

type SearchResultProps = {
  query: string;
  resultCount: number;
  results: typeof results;
};

function SearchResult({ query, resultCount, results }: SearchResultProps) {
  return (
    <Collapsible
      defaultOpen
      className="w-full overflow-hidden rounded-card border bg-card"
    >
      <Collapsible.Trigger>
        {({ isOpen, toggle }) => (
          <button
            type="button"
            aria-expanded={isOpen}
            className="flex w-full items-center gap-2 py-2 px-4 text-left text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset "
            onClick={toggle}
          >
            <PiSparkle
              aria-hidden="true"
              className="size-4 shrink-0"
            />
            <span className="min-w-0 flex-1 truncate text-sm">{query}</span>
            <span className="flex shrink-0 items-center gap-1 text-muted-foreground text-xs">
              <span>{resultCount} results</span>
              <PiCaretDown
                aria-hidden="true"
                className={`size-4 transition-transform ${isOpen ? 'rotate-180' : ''}`}
              />
            </span>
          </button>
        )}
      </Collapsible.Trigger>
      <Collapsible.Content className="overflow-visible">
        <div className="space-y-1 pb-2 px-2" role="list">
          {results.map((result) => (
            <a
              className="flex items-center gap-2 rounded-control-sm px-2 py-2 transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
              key={result.title}
              href={result.url}
              target="_blank"
              rel="noreferrer"
              role="listitem"
            >
              <img
                src={result.favicon}
                alt=""
                aria-hidden="true"
                className="size-4 shrink-0 object-contain"
              />
              <span className="min-w-0 flex-1 truncate text-foreground text-sm">
                {result.title}
              </span>
              <span className="shrink-0 text-muted-foreground text-xs">
                {result.domain}
              </span>
            </a>
          ))}
        </div>
      </Collapsible.Content>
    </Collapsible>
  );
}

export default function CustomResultDemo() {
  return (
    <div className="w-full max-w-xl">
      <ToolCall
        name="webSearch"
        state="output-available"
        input={{ query: 'Extended thinking UI component design' }}
      >
        <SearchResult
          query="Claude.ai extended thinking UI component design"
          resultCount={9}
          results={results}
        />
      </ToolCall>
    </div>
  );
}

API

ToolCall

PropDescriptionTypeDefault
nameTool name shown in the trigger row.string
stateCurrent invocation state.ToolCallState
inputInvocation input rendered as formatted JSON.unknown
outputInvocation output rendered as formatted JSON.unknown
errorTextError content rendered in the error state.string
childrenCustom result renderer replacing the output JSON block.ReactNode
classNameClasses merged onto the disclosure root.string

ToolCallState

type ToolCallState =
  | 'input-streaming'
  | 'input-available'
  | 'output-available'
  | 'output-error';