showcase/shell-docs/src/content/docs/generative-ui/tool-rendering.mdx
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.
<InlineDemo demo="tool-rendering" />Render tool calls when you want to:
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.
<Snippet cell="tool-rendering-default-catchall" region="default-catchall-zero-config" title="frontend/src/app/page.tsx — useDefaultRenderTool()" />
Here's what the built-in status card looks like for each tool call:
<InlineDemo demo="tool-rendering-default-catchall" />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:
<Snippet cell="tool-rendering-custom-catchall" region="use-default-render-tool-wildcard" title="frontend/src/app/page.tsx — custom wildcard renderer" />
Here's the branded catch-all in action, where every tool call gets the same on-brand card:
<InlineDemo demo="tool-rendering-custom-catchall" />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:
The flight renderer follows the same pattern with a different component and schema:
<Snippet region="render-flight-tool" title="frontend/src/app/page.tsx — flight renderer" /> <Callout type="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. </Callout>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:
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: