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#

Declare the component as an external tool

Agno's AG-UI interface does not forward the request's tool definitions to the model. The model only sees tools declared on the Agent, so a component registered with useComponent needs a matching declaration whose body stays empty. Mark it external_execution=True: Agno pauses the run, the browser renders the component, and the run resumes with the result.

The name and the arguments have to match the useComponent registration exactly. The docstring is what the model reads, so describe when to call it there.

src/agents/chart_agent.py
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools import tool


@tool(external_execution=True)
def render_bar_chart(title: str, data: list[dict]):
    """
    Render a bar chart in the chat.

    Call this whenever the user asks for a chart or a comparison.

    Args:
        title (str): A concise chart title.
        data (list[dict]): Items shaped `{label, value}`.
    """


agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    db=db,
    tools=[render_bar_chart],
    instructions=SYSTEM_PROMPT,
)

Give the agent somewhere to store the paused run

Agno stores the paused run before the browser can return its result, so the Agent that owns the external tool needs a database. An agent with an external tool and no db cannot resume after the browser answers.

src/agents/chart_agent.py
from agno.db.sqlite import SqliteDb

db = SqliteDb(db_file="tmp/agno.db")

SqliteDb needs sqlalchemy installed. For production use durable shared storage such as PgDb, because an ephemeral container file cannot resume a run on another instance.

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.

page.tsx
  useComponent({    name: "render_bar_chart",    description: "Display a bar chart with labeled numeric values.",    parameters: barChartPropsSchema,    render: BarChart,  });