Display components
Register React components that your agent can render in the chat.
"use client";import React from "react";import { CopilotChat, CopilotKit, useComponent,} from "@copilotkit/react-core/v2";import { BarChart, barChartPropsSchema } from "./bar-chart";import { PieChart, pieChartPropsSchema } from "./pie-chart";import { useSuggestions } from "./suggestions";function Chat() { useComponent({ name: "render_bar_chart", description: "Display a bar chart with labeled numeric values.", parameters: barChartPropsSchema, render: BarChart, }); useComponent({ name: "render_pie_chart", description: "Display a pie chart with labeled numeric values.", parameters: pieChartPropsSchema, render: PieChart, }); useSuggestions(); return ( <div className="flex justify-center items-center h-screen w-full"> <div className="h-full w-full max-w-4xl"> <CopilotChat agentId="gen-ui-tool-based" className="h-full rounded-2xl" /> </div> </div> );}export default function ControlledGenUiDemo() { return ( <CopilotKit runtimeUrl="/api/copilotkit" agent="gen-ui-tool-based"> <Chat /> </CopilotKit> );}What is this?#
Render-only generative UI lets you register React components as tools your agent can invoke. When the agent calls the tool, CopilotKit renders your component directly in the chat with the tool's arguments as props; no handler logic or user interaction required.
useComponent({
name: "showChart",
description: "Populate data and show the user a chart",
parameters: ChartProps,
render: Chart
});
export const ChartProps = z.object({
title: z.string(),
data: z.array(z.object({ label: z.string(), value: z.number() })),
});
export function Chart({ title, data }: z.infer<typeof ChartProps>) {
return (
<div>
<h3>{title}</h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={data}>
<XAxis dataKey="label" /><YAxis /><Tooltip />
<Bar dataKey="value" fill="#6366f1" />
</BarChart>
</ResponsiveContainer>
</div>
);
}When should I use this?#
Use render-only generative UI when you want to:
- Display rich UI (cards, charts, tables) inline in the chat
- Show structured data from agent responses
- Render previews, status indicators, or visual feedback
- Let the agent present information beyond plain text
How it works in code#
Nothing to wire on the agent
The AG-UI Mastra adapter turns the request's tool definitions into Mastra
client tools and passes them to agent.stream() on every run, so the agent
declares none of its own. Register the component with useComponent and
leave tools to your real backend tools.
import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";
// `render_bar_chart` is intentionally NOT declared here. It is a
// frontend-only tool registered via `useComponent`, and the adapter
// forwards it as a client tool on every run.
export const chartAgent = new Agent({
id: "chart-agent",
name: "Chart Agent",
model: openai("gpt-4o"),
instructions: SYSTEM_PROMPT,
});Tell the model when to call it
This is the part that is easy to miss. The tool arrives on every run, but a
model with no instruction about it will answer in prose and never call it.
Name the tool in instructions and say what it is for.
const SYSTEM_PROMPT = `You are a data visualization assistant.
When the user asks for a chart, call the frontend \`render_bar_chart\` tool
with a concise title and a \`data\` array of \`{label, value}\` items.
Keep chat responses brief and let the chart do the talking.`;The renderer component receives the tool's arguments as typed props and mounts inline in the chat. Below is the chart renderer wired up in the canonical demo — the agent emits the data, the component draws it.
useComponent({ name: "render_bar_chart", description: "Display a bar chart with labeled numeric values.", parameters: barChartPropsSchema, render: BarChart, });