Most AI applications still look like a conversation. You type a request, wait a moment, and get a wall of text back.

That works well when you need an explanation. But what if you're comparing subscription plans, exploring a list of products, or trying to adjust a budget? A paragraph can give you the information, but a table, a set of cards, or a few interactive controls might be easier to use.

That's what drew me to generative UI. Instead of making the user translate an answer into an action, the assistant can respond with an interface suited to the task.

In this article, I'll look at how the new generative UI features in CopilotKit work, where they might be useful, and how they compare with json-render and OpenUI. There is also a demo section where I'll walk through a small application.

TL;DR

  • Generative UI lets an AI assistant assemble an interface in response to a request, rather than replying only with text.
  • CopilotKit gives the agent a catalog of components to choose from. The resulting UI can appear in the chat or in designated areas of the application.
  • You can start with a standard catalog, add your own components, connect them to application state and actions, and choose between tool and markdown transports.
  • The new components and surfaces feature is experimental. The API can change, so check the current documentation before building around it.
  • This article is an introduction to the feature and its potential uses.

What Is Generative UI, and Why Does It Matter?

Imagine asking an assistant: "Which of these plans is best for a team of ten?"

The assistant could describe each plan in a few paragraphs. Or it could show a comparison table, highlight the differences, and offer a control for changing the team size.

Generative UI is the second approach. The model helps decide what interface to show and what information to put in it. The application then renders that interface using components available to it.

The important distinction is that the model doesn't have to write a new React application on every request. In a component-based setup, the developer defines the building blocks and their behavior. The assistant selects and combines them within those boundaries.

This doesn't mean every answer needs a dashboard. If I ask for the definition of a term, a sentence is probably enough. Generative UI becomes interesting when the answer is something I want to compare, explore, edit, or act on.

How Generative UI Works in CopilotKit

CopilotKit connects an AI agent to your application and lets it render UI from a catalog you provide. There are a few pieces worth understanding before we get to the demo.

The catalog: what the agent can use

A catalog describes the components available to the agent, including what they do and which props they accept. CopilotKit provides a standard catalog with components for layouts, forms, tables, charts, maps, and more.

You can also register your own components with createGenerativeUI. This is useful when your application already has a design system or when a generic card isn't enough. For example, a store might expose its own ProductCard with the fields it expects.

You can extend the standard catalog, remove components you don't need, or replace standard components with your own implementations. The descriptions matter too: they help the agent understand when a component is appropriate.

Surfaces: where the UI appears

A surface is a piece of generated UI rendered in a target. The chat is one target, but you can also mark an area of the page as a target.

Think of a shop with a featured products section. A user could ask the assistant to show cheaper options, and the agent could update that section instead of returning another list in the chat. The developer can specify which components are allowed in that area and provide fallback content for when there is no generated surface.

The model still needs the right data and instructions. A surface doesn't automatically connect an assistant to a product database.

Transports: how the UI gets there

CopilotKit supports two transports:

  • Tool mode, the default, lets the agent send UI operations through a render_ui tool. The agent needs to pass the run's tools and context to the model.
  • Markdown mode lets the agent write UI operations in a2ui code blocks within its response. The chat renders those blocks as UI, with ordinary text around them. The agent still needs the catalog and instructions in its context.

Both use the same catalogs and surfaces. Tool mode is a reasonable starting point when the agent supports the required tools. Markdown mode is useful when the agent streams text but can't use client tools, or when you want text and UI together in the same reply.

State, actions, and protocols

A generated button is only useful if it does something. CopilotKit lets you connect components to application state and functions. For example, a product card can display a live cart quantity and call your application's add-to-cart handler. Inputs can also preserve values entered by the user.

Under the hood, the components and surfaces feature implements A2UI 1.0 at the spec revision listed in the documentation. A2UI describes the UI operations; AG-UI provides an event-based protocol for communication between agents and user-facing applications. They solve different parts of the problem.

One detail is worth repeating: CopilotKit marks this feature as experimental, and A2UI 1.0 is still a candidate. CopilotKit supports the scope listed in its support matrix, not necessarily every part of the specification.

What Can You Build With It?

The most useful examples aren't necessarily the most complicated ones. A small interface that helps someone take the next step can be more valuable than a page full of generated widgets.

Here are a few possibilities:

  • A plan comparison: Show a table of plans, then let the user adjust the number of seats and see how that changes the estimate.
  • A product discovery assistant: Display product cards in a page section and update them when the user changes their preferences.
  • A data exploration tool: Present a chart alongside filters or other controls for exploring a dataset.
  • An interactive form: Ask for missing information through inputs instead of making the user format everything in a chat message.
  • An assistant inside an existing application: Use your own components and connect generated controls to real app state and actions.

These are patterns you can build, not features that appear automatically. A useful result still depends on the components, data, model, and application logic you provide.

Choosing a Model

The model matters because it has to follow the catalog instructions and produce valid UI operations, not just write a convincing answer. If you're using tool mode, check that your chosen model and agent setup support the required tool calls. For markdown mode, the agent needs to stream text and receive the catalog instructions.

For experimentation, free models can be a useful starting point. For example, OpenRouter's free model router selects from available free models, with availability and rate limits subject to change. You can also choose a specific free model if one is available. The router can select different models between requests, so use a fixed model when you want a more consistent comparison.

That doesn't mean every CopilotKit demo automatically supports OpenRouter or any other provider. The agent and its model adapter need to be configured for the provider you choose. Check the demo's setup instructions and the model's capabilities before changing it.

Also consider privacy. A free model is not necessarily a private model: prompts, responses, and any application data sent to a hosted provider leave your machine. Check the provider's data retention and training policies, along with any intermediary's settings. Use sample data while experimenting, and keep API keys on the server and out of your repository. Running a model locally can be another option, but only if your agent setup supports it and the model handles the required output reliably.

Building and Running the Demo

Now, let’s create a simple demo showcasing how to work with GenerativeUI in CopilotKit. To get started, I’ll create a GitHub repository:

GitHub

Next, we’ll copy it to our local computer and use npm init to create a package.json file. Then, to enable our application to work with GPT, we need to create an .env.local file. In the end, your project should look something like this:

files

Now, let’s set everything up according to the documentation so that we can get started. Please note that you must have Node.js installed.

npm install @copilotkit/react-core @copilotkit/runtime @tanstack/ai @tanstack/ai-openai 

Also, for the application to work, you need to install Next.js as well.

Some standard catalog components need a third-party library. For example, the Map component needs maplibre-gl.

 "dependencies": { "@copilotkit/react-core": "^1.85.0", "@copilotkit/runtime": "^1.85.0", "@tanstack/ai": "^1.2.0", "@tanstack/ai-openai": "^1.1.1", "maplibre-gl": "^5.24.0", "next": "^15.5.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@types/node": "^22.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "typescript": "^5.9.0" } 

Now, let’s create some simple components to get our entire application up and running. It’s worth noting that you can take full advantage of CopilotKit with just 5 minutes of setup.

app/api/copilotkit/route.ts

import { BuiltInAgent, CopilotRuntime, convertInputToTanStackAI, createCopilotRuntimeHandler, } from "@copilotkit/runtime/v2"; import { chat } from "@tanstack/ai"; import { openaiText } from "@tanstack/ai-openai"; const agent = new BuiltInAgent({ type: "tanstack", factory: ({ input, abortController }) => { if (!process.env.OPENAI_API_KEY) { throw new Error( "OPENAI_API_KEY is not configured. Add it to .env.local.", ); } const { messages, systemPrompts, tools } = convertInputToTanStackAI(input); return chat({ adapter: openaiText("gpt-6-luna"), messages, systemPrompts: [ ...systemPrompts, "Respond in English. If the user asks to compare options, use render_ui and the Table component from the catalog to show the result as a table. If the user asks to visualize data, use render_ui with an appropriate chart and, when useful, interactive controls such as a slider.", ], tools, threadId: input.threadId, abortController, }); }, }); const handler = createCopilotRuntimeHandler({ mode: "single-route", runtime: new CopilotRuntime({ agents: { default: agent }, // Optional: generative UI can be used without setting this flag. generativeUI: true, }), }); export const GET = handler; export const POST = handler; export const OPTIONS = handler; 

layout.tsx

import "@copilotkit/react-core/v2/styles.css"; import "./globals.css"; import { Providers } from "./providers"; export const metadata = { title: "CopilotKit — Generative UI", description: "Demo", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body> <Providers>{children}</Providers> </body> </html> ); } 

page.tsx

import { CopilotChat } from "@copilotkit/react-core/v2"; export default function Home() { return ( <main className="page"> <section className="intro"> <p className="eyebrow">COPILOTKIT · GENERATIVE UI</p> <h1>Answers that become an interface</h1> <p className="description"> Ask the agent to compare plans and it will display the answer in a table right in the chat. Try refining the result with a follow-up message. </p> <div className="example-prompt"> <span className="prompt-label">Try it</span> <span>Compare Starter, Team, and Enterprise in a table</span> </div> </section> <section className="chat-panel" aria-label="Chat with an AI agent"> <CopilotChat className="demo-chat" /> </section> <footer>Powered by CopilotKit, TanStack AI, and OpenAI</footer> </main> ); } 

providers.tsx

"use client"; import { CopilotKitProvider } from "@copilotkit/react-core/v2"; import { standardUI } from "@copilotkit/react-core/v2/generative-ui"; import type { ReactNode } from "react"; export function Providers({ children }: { children: ReactNode }) { return ( <CopilotKitProvider runtimeUrl="/api/copilotkit" useSingleEndpoint generativeUI={{ ui: standardUI }} > {children} </CopilotKitProvider> ); } 

If you have done everything correctly, then upon running npm run dev, the following page will appear in your browser:

app

Now we can use our application; all that remains is to create an API key.

Key

OPENAI_API_KEY=... 

Now, let’s try to write something in the chat. Let’s say I sell seats in a cinema and I want to visualize some data so that it’s easier for me to understand it if I’m an accountant. ChatGPT and other assistants now offer interactive experiences in some contexts, but a standalone chat may not provide the app-specific controls and data flow this task needs. That’s where the generative UI in our application can help.

app diagram

As you can see, we can now adjust the number of seats, and our chart will change accordingly. In a plain chat, changing the number might instead require another prompt, unless the assistant provides a suitable interactive feature.

CopilotKit vs. json-render vs. OpenUI

All three tools help developers turn model output into interfaces built from components. For me, the interesting difference is how they fit into an application, especially when an assistant needs to work both inside the chat and elsewhere on the page.

What I'm looking for CopilotKit json-render OpenUI
Starting with ready-made components Standard catalog with layouts, forms, tables, charts, maps, and more. Pre-built shadcn/ui components are available, or you can define your own catalog and registry. Offers a component library and supports defining your own components.
Using an existing design system Extend the catalog, remove components, or replace standard components with your own. Define component types in a catalog and map them to your implementations in a registry. Define a library using your own components.
UI in the chat and on the page Chat rendering and page targets are part of the same surfaces workflow. Targets can restrict components and provide fallback content. Supports inline conversational UI and standalone generated UI. Provides a renderer for generated interfaces and an agent interface for chat.
How the model sends UI Choose tool mode or markdown mode while using the same catalogs and surfaces. Generates JSON specifications, including streamed JSONL patches. Generates interfaces using its line-oriented OpenUI Lang.
Connecting interactions Bind app state, preserve user input, and connect component events to application functions. Supports state binding and action handlers. Supports interactive components and actions.
Investigating a bad result CopilotKit Inspector shows the catalog, surfaces, rejected operations, and model instructions. Uses its catalog and registry to define and render the generated specification. Offers optional repair and reliability monitoring tools.
Agent experience Designed to connect agents to an application, with generative UI integrated into the workflow. Focuses on generating and rendering UI specifications. Offers an agent interface and integrations with agent frameworks, including CopilotKit.

For an application already using CopilotKit, the combination of a standard catalog, surfaces, agent integration, and Inspector is a compelling starting point. I especially like that I can keep a conversational interface while placing the result elsewhere on the page.

That said, the table is about the features and workflows each tool offers, not a claim that one will always produce better results. json-render's explicit JSON and registry approach may suit one project, while OpenUI's dedicated language and tooling may suit another. CopilotKit's components and surfaces feature is also experimental, which is worth considering before relying on it in production.

What I Like About the Approach

The idea I keep coming back to is that an assistant doesn't need to live entirely inside a chat. A conversation can be the way a user expresses intent, while the result appears in the part of the application where it belongs.

I also like the balance between flexibility and control. The agent can choose how to compose a response, but the developer still owns the components and decides which actions they expose. Starting with a catalog and adding application-specific components feels more practical than asking a model to generate arbitrary frontend code.

There are limits, though. A valid component tree can still contain incorrect information or a confusing layout. Real applications need good data, clear component descriptions, accessible components, error handling, and authorization for actions. Catalog validation is not a substitute for any of those.

CopilotKit's Inspector is useful here: it can show the catalog, surfaces, rejected operations, and the instructions sent to the model. That gives you somewhere concrete to look when a response doesn't render as expected.

Who Is This For?

Generative UI is worth exploring if you're building an assistant that helps users work with structured information or complete tasks inside an application. Internal tools, dashboards, shopping experiences, and planning applications are all plausible starting points.

It may be less useful if your assistant mostly answers straightforward questions, or if the same fixed interface already handles the task well. Generated UI adds another moving part to test and maintain. It should earn its place by making the experience clearer or more useful.

FAQ

What is the difference between generative UI and a chatbot?

A chatbot is an interface for conversation. Generative UI is a way to create or assemble an interface in response to a request. You can use them together: a chatbot can return text alongside generated tables, forms, or other components.

Does CopilotKit generate arbitrary React code?

In the components and surfaces approach described here, the agent produces UI operations using components from a catalog. Your application renders the registered components. You define the components and their behavior rather than letting the model execute arbitrary generated React code.

Can CopilotKit render generative UI outside the chat?

Yes. You can mark an area of the page as a target with GenerativeUISurface in React, or the corresponding surface component in Angular. The agent can render there using the components allowed for that target.

Can I use my own components and design system?

Yes. You can register custom components in a catalog, extend the standard catalog, or replace standard components. The standard components also support theming.

Can I use a free model for generative UI?

Potentially, yes. It depends on the model's capabilities and whether your agent is configured to connect to its provider. Free models can have usage limits and may differ in how reliably they follow the UI format. Check current availability, pricing, and privacy terms before using one.

Conclusion

Generative UI is not a replacement for thoughtful interface design. It gives developers another way to connect a user's intent to the parts of an application that can help.

CopilotKit's combination of catalogs, surfaces, and agent integration makes that idea interesting to explore. The next question is whether the generated interface actually makes a task easier than a good static UI or a simple text answer.

Resources:

Thanks for reading this article! ❤️

I'd love to hear your thoughts on this in the comments!

Thanks!

💎 Check out the CopilotKit repository ☆