7acadae
CopilotKitDocs
  • Docs
  • Integrations
  • Reference
Get Started
QuickstartCoding Agents
Concepts
ArchitectureGenerative UI OverviewOSS vs Enterprise
Agentic Protocols
OverviewAG-UIAG-UI MiddlewareMCPA2A
Build Chat UIs
Prebuilt Components
CopilotChatCopilotSidebarCopilotPopup
Custom Look and Feel
CSS CustomizationSlots (Subcomponents)Fully Headless UIReasoning Messages
Multimodal AttachmentsVoice
Build Generative UI
Controlled
Tool-based Generative UITool RenderingState RenderingReasoning
Your Components
Display ComponentsInteractive Components
Declarative
A2UIDynamic Schema A2UIFixed Schema A2UI
Open-Ended
MCP Apps
Adding Agent Powers
Frontend ToolsShared State
Human-in-the-Loop
HITL OverviewPausing the Agent for InputHeadless Interrupts
Sub-AgentsAgent ConfigProgrammatic Control
Agents & Backends
Built-in Agent
Backend
Copilot RuntimeFactory ModeAG-UI
Runtime Server AdapterAuthentication
Built-in Agent (TanStack AI)
Advanced ConfigurationMCP ServersModel SelectionServer Tools
Observe & Operate
InspectorVS Code Extension
Troubleshooting
Common Copilot IssuesError Debugging & ObservabilityDebug ModeAG-UI Event InspectorHook ExplorerError Observability Connectors
Enterprise
CopilotKit PremiumHow the Enterprise Intelligence Platform WorksHow Threads & Persistence WorkObservabilitySelf-Hosting IntelligenceThreads
Deploy
AWS AgentCore
What's New
Full MCP Apps SupportLangGraph Deep Agents in CopilotKitA2UI Launches with full AG-UI SupportCopilotKit v1.50Generative UI Spec SupportA2A and MCP Handshake
Migrate
Migrate to V2Migrate to 1.8.2
Other
Contributing
Code ContributionsDocumentation Contributions
Anonymous Telemetry
Built-in Agent (TanStack AI)Build Generative UITool Rendering

Tool Rendering

Render your agent's tool calls with custom UI components.

What is this?#

Tools are how an LLM invokes predefined, typically-deterministic functions. Tool rendering lets you decide how each of those tool calls appears in the chat. Instead of showing raw JSON, you register a React component that draws a branded card for the call (arguments, live status, and the eventual result). This is the Generative UI variant CopilotKit calls tool rendering.

Info

Free course: See this pattern built end-to-end in Build Interactive Agents with Generative UI — a free DeepLearning.AI short course taught by CopilotKit's CEO covering the full Generative UI spectrum (Controlled, Declarative, and Open-Ended).

Live Demo: Built-in Agent (TanStack AI) — tool-renderingOpen full demo →
Info

Free course: See this pattern built end-to-end in Build Interactive Agents with Generative UI — a free DeepLearning.AI short course taught by CopilotKit's CEO covering the full Generative UI spectrum (Controlled, Declarative, and Open-Ended).

When should I use this?#

Render tool calls when you want to:

  • Show users exactly what tools the agent is invoking and with what arguments
  • Display live progress indicators while a tool executes
  • Render rich, polished results once a tool completes
  • Give tool-heavy agents a transparent, on-brand chat experience

Default tool rendering (zero-config)#

The simplest entry point: call useDefaultRenderTool() with no arguments. CopilotKit registers its built-in DefaultToolCallRenderer as the * wildcard: every tool call renders as a tidy status card (tool name, live Running → Done pill, collapsible arguments/result) without you writing any UI.

Without this hook the runtime has no * renderer and tool calls are invisible; the user only sees the assistant's final text summary.

frontend/src/app/page.tsx — useDefaultRenderTool()
L28–32
  // Opt in to CopilotKit's built-in default tool-call card. Called with
  // no config so the package-provided `DefaultToolCallRenderer` is used
  // as the wildcard renderer — this is the "out-of-the-box" UI the cell
  // is meant to showcase.
  useDefaultRenderTool();

Here's what the built-in status card looks like for each tool call:

Live Demo: Built-in Agent (TanStack AI) — tool-rendering-default-catchallOpen full demo →

Custom catch-all#

Once you want on-brand chrome, pass a render function to useDefaultRenderTool. It's a convenience wrapper around useRenderTool({ name: "*", ... }): one wildcard renderer handles every tool call, named or not:

frontend/src/app/page.tsx — custom wildcard renderer
L30–46
  // `useDefaultRenderTool` is a convenience wrapper around
  // `useRenderTool({ name: "*", ... })` — a single wildcard renderer
  // that handles every tool call not claimed by a named renderer.
  useDefaultRenderTool(
    {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      render: ({ name, parameters, status, result }: any) => (
        <CustomCatchallRenderer
          name={name}
          parameters={parameters}
          status={status as CatchallToolStatus}
          result={result}
        />
      ),
    },
    [],
  );

Here's the branded catch-all in action, where every tool call gets the same on-brand card:

Live Demo: Built-in Agent (TanStack AI) — tool-rendering-custom-catchallOpen full demo →

Per-tool renderers#

The most expressive path is one renderer per tool name. The primary tool-rendering cell wires two: get_weather draws a branded WeatherCard, search_flights draws a FlightListCard. Each renderer receives the tool's parsed arguments, a live status, and (once the agent returns) the result:

frontend/src/app/page.tsx — weather renderer
L3–32
import {
  CopilotKitProvider,
  CopilotChat,
  useRenderTool,
  useDefaultRenderTool,
} from "@copilotkit/react-core/v2";
import { z } from "zod";

export default function ToolRendering() {
  return (
    <CopilotKitProvider runtimeUrl="/api/copilotkit" useSingleEndpoint>
      <Demo />
    </CopilotKitProvider>
  );
}

function Demo() {
  useRenderTool({
    name: "get_weather",
    parameters: z.object({ location: z.string() }),
    render: ({ parameters, result, status }) => {
      return (
        <WeatherCard
          loading={status !== "complete"}
          parameters={parameters}
          result={result}
        />
      );
    },
  });

The flight renderer follows the same pattern with a different component and schema:

frontend/src/app/page.tsx — flight renderer
L11–42
import { useComponent } from "@copilotkit/react-core/v2";

declare const FlightListCard: React.ComponentType<{
  loading: boolean;
  origin: string;
  destination: string;
  flights: unknown[];
}>;

type FlightToolProps = {
  status: string;
  args?: { origin?: string; destination?: string };
  result?: { origin?: string; destination?: string; flights?: unknown[] };
};

export function FlightToolRenderer() {
  // Per-tool renderer: search_flights → branded FlightListCard.
  useComponent({
    name: "search_flights",
    render: (props: FlightToolProps) => {
      const { status, args, result } = props;
      const loading = status !== "complete";
      return (
        <FlightListCard
          loading={loading}
          origin={args?.origin ?? result?.origin ?? ""}
          destination={args?.destination ?? result?.destination ?? ""}
          flights={result?.flights ?? []}
        />
      );
    },
  });
Info

The name you pass to useRenderTool must match the tool name the agent exposes; that's how the runtime routes the call to your component.

Per-tool renderers compose with a catch-all: named renderers claim the "interesting" tools and a wildcard handles everything else. In the primary cell, the same CustomCatchallRenderer from above catches get_stock_price and roll_dice:

frontend/src/app/page.tsx — per-tool + catch-all
L34–36
  useDefaultRenderTool({
    render: GenericToolCard,
  });

The backend tool definition#

The frontend renderer only sees what the agent sends down. Here's the matching Python definition for get_weather, a standard LangChain tool, no CopilotKit-specific plumbing required:

backend/agent.py — weather tool
L1–15
import { z } from "zod";
import { toolDefinition } from "@tanstack/ai";

export const weatherTool = toolDefinition({
  name: "weather",
  description: "Get current weather for a city",
  inputSchema: z.object({
    city: z.string(),
  }),
}).server(async ({ city }) => ({
  city,
  tempF: 72,
  condition: "Partly cloudy",
  humidity: 0.45,
}));
Supported by
Built-in Agent (TanStack AI)LangGraph (Python)LangGraph (TypeScript)LangGraph (FastAPI)Google ADKMastraCrewAI (Crews)PydanticAIClaude Agent SDK (Python)Claude Agent SDK (TypeScript)AgnoAG2LlamaIndexAWS StrandsLangroidMS Agent Framework (Python)MS Agent Framework (.NET)Spring AI
On this page
What is this?When should I use this?Default tool rendering (zero-config)Custom catch-allPer-tool renderersThe backend tool definition