Components as Tools
Let your agent render rich React components directly in the chat by calling them as tools.
"use client";import { CopilotKitProvider, CopilotChat, useComponent,} from "@copilotkit/react-core/v2";export default function GenUiToolBased() { return ( <CopilotKitProvider runtimeUrl="/api/copilotkit" useSingleEndpoint> <Demo /> </CopilotKitProvider> );}function Demo() { useComponent({ name: "generate_haiku", render: HaikuCard, }); return ( <main className="p-8"> <h1 className="text-2xl font-semibold mb-4">Tool-Based Generative UI</h1> <p className="text-sm opacity-70 mb-6"> Try: “Write me a haiku about nature.” The agent calls the <code className="mx-1 px-1 bg-gray-100 rounded">generate_haiku</code> tool and the result renders inline as a typed card. </p> <CopilotChat /> </main> );}/** * HaikuCard — rendered by `useComponent({ name: "generate_haiku" })`. * * `useComponent` passes tool-call arguments directly as React props (via * `useFrontendTool`'s `render: ({ args }) => <Component {...args} />`). * The D5 fixture sends: { japanese, english, image_name, gradient }. */// eslint-disable-next-line @typescript-eslint/no-explicit-anyfunction HaikuCard(props: any) { const japaneseLines: string[] = props.japanese ?? []; const englishLines: string[] = props.english ?? props.lines ?? []; const gradient: string | undefined = props.gradient; const topic: string | undefined = props.topic; const hasContent = japaneseLines.length > 0 || englishLines.length > 0; if (!hasContent) { return ( <div data-testid="haiku-card" className="border rounded p-3 my-2 opacity-70 text-sm" > Composing haiku{topic ? ` about ${topic}` : ""}… </div> ); } return ( <div data-testid="haiku-card" className="border rounded p-4 my-2 bg-amber-50" style={gradient ? { background: gradient } : undefined} > <div className="font-medium mb-2">Haiku{topic ? ` — ${topic}` : ""}</div> {japaneseLines.length > 0 && ( <div className="mb-2"> {japaneseLines.map((line: string, i: number) => ( <div key={i} data-testid="haiku-japanese-line" className="text-lg leading-relaxed" > {line} </div> ))} </div> )} <div className="font-serif italic whitespace-pre-line text-lg leading-relaxed"> {englishLines.map((line: string, i: number) => ( <div key={i} data-testid="haiku-english-line"> {line} </div> ))} </div> </div> );}What is this?#
Tool-based Generative UI is the simplest form of Generative UI: you register
a React component with useComponent, and CopilotKit exposes it to the
agent as a tool. When the agent calls the tool, CopilotKit renders your
component inline in the chat, passing the tool's arguments straight through
as typed props.
Unlike tool rendering, which wraps a real backend tool in a custom UI, tool-based GenUI is the component. There is no handler, no user interaction, no server-side execution. The agent decides when to show it, populates the data, and CopilotKit paints it.
When should I use this?#
Use useComponent when you want to:
- Display rich UI (cards, charts, tables, dashboards) inline in the chat
- Show structured data the agent has derived from its reasoning
- Render previews, status indicators, or visual summaries
- Let the agent present information beyond plain text
For components that need user interaction, see Human-in-the-loop. For operational transparency around a real backend tool, see Tool rendering.
How it works in code#
useComponent takes a name, a Zod schema for its props, and the component
to render. The runtime registers it as a frontend tool so the agent can
discover it, and Zod validates the LLM's arguments before they reach your
component.
import { useComponent } from "@copilotkit/react-core/v2";import { z } from "zod";// Stand-ins for the locally-authored bar chart component + its prop// schema. In a real page, these live in the demo directory (e.g.// `./bar-chart.tsx` exporting `BarChart` and `barChartPropsSchema`).declare const BarChart: React.ComponentType<{ title: string; data: { label: string; value: number }[];}>;declare const barChartPropsSchema: z.ZodSchema;export function BarChartRenderer() { useComponent({ name: "render_bar_chart", description: "Display a bar chart with labeled numeric values.", parameters: barChartPropsSchema, render: BarChart, });The component itself is ordinary React: it reads only its props and can stream in as the agent fills the payload. The example above uses Recharts for the bar chart; it doesn't know anything about CopilotKit.
The name you pass to useComponent is what the agent sees as the tool
name. Make it a verb like render_bar_chart or show_weather so the LLM
reliably picks it when the user asks for that visualization.
