Elements

11 / 37 · Tool use

Tool call

One tool invocation with its request and result tucked behind a disclosure.

Installation

npx shadcn@latest add "@assistant-ui/elements-tool-call"

Usage

import { ToolCall } from "@/components/elements/tool-call";

<ToolCall
  label="Searched"
  activeLabel="Searching"
  query="composer props"
  request='{"q":"composer"}'
  result='{"hits":2}'
  running={false}
  open
  onOpenChange={setOpen}
/>

Props

activeLabel*stringVerb shown with a shimmer while the tool is running, for example Searching the docs.
label*stringTool name shown on the collapsed trigger row.
query*stringShort chip next to the label summarizing the call target.
request*stringRequest payload shown inside the expanded disclosure.
result*stringResult payload shown once the call is no longer running.
running*booleanWhen true a spinner replaces the success check on the trigger.
open*booleanWhether the request and result panel is expanded.
onOpenChange*(open: boolean) => voidCalled when the user expands or collapses the call.
classNamestringExtra classes merged onto the root.

Source

tool-call.tsx
"use client";

import { CheckIcon, ChevronRightIcon } from "lucide-react";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
import { collapsePanel, field, mono, SwapLabel } from "./surfaces";

export interface ToolCallProps {
  label: string;
  activeLabel: string;
  query: string;
  request: string;
  result: string;
  running: boolean;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  className?: string;
}

export function ToolCall({
  label,
  activeLabel,
  query,
  request,
  result,
  running,
  open,
  onOpenChange,
  className,
}: ToolCallProps) {
  return (
    <Collapsible
      open={open}
      onOpenChange={onOpenChange}
      className={cn("w-full max-w-sm", className)}
    >
      <CollapsibleTrigger className="group/trigger text-foreground/55 hover:text-foreground/90 flex items-center gap-2 rounded-md py-1 text-[13.5px] transition-colors outline-none">
        <ChevronRightIcon className="size-3.5 shrink-0 opacity-60 transition-transform duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-open/trigger:rotate-90 group-data-panel-open/trigger:rotate-90 motion-reduce:transition-none" />
        <SwapLabel active={running ? 0 : 1} className="text-start">
          <span className="relative inline-block leading-none">
            <span>{activeLabel}</span>
            <span
              aria-hidden
              className="shimmer pointer-events-none absolute inset-0 motion-reduce:animate-none"
            >
              {activeLabel}
            </span>
          </span>
          <>{label}</>
        </SwapLabel>
        <span
          className={cn(
            mono,
            "bg-foreground/[0.06] text-foreground/70 rounded-md px-1.5 py-0.5",
          )}
        >
          {query}
        </span>
        <span className="ms-auto flex w-4 items-center justify-end">
          {!running && (
            <CheckIcon className="fade-in zoom-in-90 animate-in size-3.5 text-emerald-500 duration-200" />
          )}
        </span>
      </CollapsibleTrigger>
      <CollapsibleContent className={cn(collapsePanel, "outline-none")}>
        <div className={cn(field, "mt-2 overflow-hidden rounded-2xl text-xs")}>
          <div className="px-3.5 pt-2.5 pb-2">
            <p className={cn(mono, "text-foreground/35 mb-1")}>Request</p>
            <p className="text-foreground/55 font-mono">{request}</p>
          </div>
          <div className="bg-foreground/[0.06] mx-3.5 h-px" />
          <div className="px-3.5 pt-2 pb-2.5">
            <p className={cn(mono, "text-foreground/35 mb-1")}>Result</p>
            <p className="text-foreground/90">{result}</p>
          </div>
        </div>
      </CollapsibleContent>
    </Collapsible>
  );
}