CopilotKit

Open Generative UI

Let agents generate fully interactive HTML/CSS/JS UIs that stream live into the chat.


"use client";/** * Open-Ended Generative UI — minimal setup. * * Enabling `openGenerativeUI` in the runtime (see * `src/app/api/copilotkit-ogui/route.ts`) is all that's needed — the runtime * middleware streams agent-authored HTML + CSS to the built-in * `OpenGenerativeUIActivityRenderer`, which mounts it inside a sandboxed * iframe. No custom sandbox functions, no custom tools — just chat. */import { CopilotKitProvider, CopilotChat } from "@copilotkit/react-core/v2";const VISUALIZATION_DESIGN_SKILL = `When generating UI with generateSandboxedUi, your goal is to produce a polished, intricate, EDUCATIONAL visualisation that teaches the concept the user asked about. Treat the output like a figure from a well-designed textbook or explorable-explanation — not a bare-bones demo.Geometry + rendering:- Use inline SVG (preferred) or <canvas> for geometric content — NEVER stack dozens of <div>s to draw shapes.- Fit content within a ~600x400 content area with ~16-24px of edge padding. Use viewBox + preserveAspectRatio so it scales cleanly.Animation:- Prefer CSS @keyframes + transitions over JS setInterval. Use animation-timing-function ease-in-out or cubic-bezier; 300-900ms per cycle.- When JS timing IS needed, use requestAnimationFrame.Labels + legend + annotations:- EVERY axis gets a label. EVERY colour-coded series gets a legend swatch.- Add short text callouts that explain what the viewer is watching.- Include a 1-line title + 1-line subtitle at the top.Palette: indigo #6366f1 (primary motion), emerald #10b981 (stable), amber #f59e0b (active), rose #ef4444 (contrast), slate #64748b (axes).Typography: system-ui sans-serif. Title 16-18px/600. Axis labels 11-12px.Output contract: Emit initialHeight first (480-560), then placeholderMessages (2-3 short lines), then css, then html with ONE root container.`;export default function OpenGenUiDemo() {  // Minimal Open Generative UI frontend: the built-in activity renderer is  // registered by CopilotKitProvider, so a plain <CopilotChat /> is enough —  // no custom tool renderers, no activity-renderer registration.  // We DO pass `openGenerativeUI.designSkill` to swap in visualisation-tuned  // guidance in place of the default shadcn design skill.  return (    <CopilotKitProvider      runtimeUrl="/api/copilotkit-ogui"      useSingleEndpoint      openGenerativeUI={{ designSkill: VISUALIZATION_DESIGN_SKILL }}    >      <main className="p-8">        <h1 className="text-2xl font-semibold mb-4">Open Generative UI</h1>        <p className="text-sm opacity-70 mb-6">          Try: &ldquo;Visualize how a neural network performs a forward          pass.&rdquo; The agent authors HTML + CSS that mounts inside a          sandboxed iframe inline in the chat.        </p>        <CopilotChat />      </main>    </CopilotKitProvider>  );}

What is this?#

Open Generative UI lets the agent generate complete, sandboxed UI on the fly (HTML, CSS, and JavaScript) and stream it live into the chat. The user sees the interface build in real time: styles apply first, then HTML streams in progressively, and finally JavaScript expressions execute one by one.

Free course: See this pattern built end-to-end in Build Interactive Agents with Generative UI — a free DeepLearning.AI short course taught by CopilotKit's CEO covering the full Generative UI spectrum (Controlled, Declarative, and Open-Ended).

Key benefits:

  • No predefined components — the agent creates any UI it needs, on demand
  • Live streaming — HTML streams into a preview as it's generated
  • CDN libraries — the generated UI can load Chart.js, D3, Three.js, etc. via <script> tags
  • Secure sandboxing — content runs in an isolated iframe without same-origin access
  • Sandbox functions — optionally expose host functions to the generated UI for two-way communication

Minimal setup#

Turning on Open Generative UI takes one flag in the runtime plus a plain <CopilotChat /> on the frontend; the built-in activity renderer is auto-registered by CopilotKit, so no extra wiring is needed.

Enable it in the runtime#

Add OpenGenerativeUIMiddleware to your runtime configuration:

route.ts
const runtime = new CopilotRuntime({  agents: { default: createOguiAgent() },  runner: new InMemoryAgentRunner(),  openGenerativeUI: {    agents: ["default"],  },});

The OpenGenerativeUIMiddleware then converts the agent's streamed generateSandboxedUi tool call into open-generative-ui activity events, which the built-in OpenGenerativeUIActivityRenderer mounts inside a sandboxed iframe.

Drop <CopilotChat /> into the page#

Wrap your app in CopilotKit and render <CopilotChat> — no extra props needed:

page.tsx
  // Minimal Open Generative UI frontend: the built-in activity renderer is  // registered by CopilotKitProvider, so a plain <CopilotChat /> is enough —  // no custom tool renderers, no activity-renderer registration.  // We DO pass `openGenerativeUI.designSkill` to swap in visualisation-tuned  // guidance in place of the default shadcn design skill.  return (    <CopilotKitProvider      runtimeUrl="/api/copilotkit-ogui"      useSingleEndpoint      openGenerativeUI={{ designSkill: VISUALIZATION_DESIGN_SKILL }}    >      <main className="p-8">        <h1 className="text-2xl font-semibold mb-4">Open Generative UI</h1>        <p className="text-sm opacity-70 mb-6">          Try: &ldquo;Visualize how a neural network performs a forward          pass.&rdquo; The agent authors HTML + CSS that mounts inside a          sandboxed iframe inline in the chat.        </p>        <CopilotChat />      </main>    </CopilotKitProvider>  );

That's it. Ask the agent "build me a simple greeting card" to see HTML stream into a sandboxed preview live.

Advanced: With app tool calling#

Sandbox functions let the generated UI call back into your host application — a generated settings panel can toggle your app's theme, a product card can push items into your cart, or a data view can ask the host to fetch data the iframe can't reach directly.

"use client";/** * Open-Ended Generative UI — ADVANCED variant with host-side sandbox functions. * * Same backend wiring as `open-gen-ui` (the runtime's * `OpenGenerativeUIMiddleware` injects the `generateSandboxedUi` tool and * streams the agent's HTML/CSS/JS into a sandboxed iframe). The advanced * twist: the provider passes `openGenerativeUI.sandboxFunctions`, which the * built-in `OpenGenerativeUIActivityRenderer` exposes inside the iframe as * `Websandbox.connection.remote.<name>(args)`. Now the agent-authored UI * can call back INTO the host page — closing the loop between LLM-authored * UI and app-side capability. */import {  CopilotKitProvider,  CopilotChat,  useConfigureSuggestions,} from "@copilotkit/react-core/v2";import { openGenUiSandboxFunctions } from "./sandbox-functions";import { openGenUiSuggestions } from "./suggestions";export default function OpenGenUiAdvancedDemo() {  return (    // Pass the sandbox-function array on the `openGenerativeUI` provider prop.    // The built-in `OpenGenerativeUIActivityRenderer` wires these as callable    // remotes inside the agent-authored iframe.    <CopilotKitProvider      runtimeUrl="/api/copilotkit-ogui"      useSingleEndpoint      openGenerativeUI={{ sandboxFunctions: openGenUiSandboxFunctions }}    >      <Demo />    </CopilotKitProvider>  );}function Demo() {  useConfigureSuggestions({    suggestions: openGenUiSuggestions,    available: "always",  });  return (    <main className="p-8">      <h1 className="text-2xl font-semibold mb-4">        Open Generative UI (Advanced)      </h1>      <p className="text-sm opacity-70 mb-6">        Try one of the suggestions. The agent authors HTML + JS that runs in a        sandboxed iframe and calls host-side functions (        <code className="mx-1 px-1 bg-gray-100 rounded">          evaluateExpression        </code>        ,<code className="mx-1 px-1 bg-gray-100 rounded">notifyHost</code>) over        a postMessage bridge.      </p>      <CopilotChat />    </main>  );}

Runtime is unchanged#

The server-side flag is identical to the minimal cell; the advanced behaviour is a pure frontend addition.

route.ts
const runtime = new CopilotRuntime({  agents: { default: createOguiAgent() },  runner: new InMemoryAgentRunner(),  openGenerativeUI: {    agents: ["default"],  },});

Register sandbox functions on the provider#

Each sandbox function is a Zod-validated, host-side bridge the agent can invoke from inside the generated iframe via Websandbox.connection.remote.<name>(args). The handler runs in the host page and its description is appended to the agent's context, so the agent knows which bridges are available when generating HTML/JS.

page.tsx
import {  CopilotKitProvider,  CopilotChat,  useConfigureSuggestions,} from "@copilotkit/react-core/v2";import { openGenUiSandboxFunctions } from "./sandbox-functions";import { openGenUiSuggestions } from "./suggestions";export default function OpenGenUiAdvancedDemo() {  return (    // Pass the sandbox-function array on the `openGenerativeUI` provider prop.    // The built-in `OpenGenerativeUIActivityRenderer` wires these as callable    // remotes inside the agent-authored iframe.    <CopilotKitProvider      runtimeUrl="/api/copilotkit-ogui"      useSingleEndpoint      openGenerativeUI={{ sandboxFunctions: openGenUiSandboxFunctions }}    >      <Demo />    </CopilotKitProvider>

How the sandbox calls you back

Inside the generated UI, the agent writes JS that calls await Websandbox.connection.remote.notifyHost({ message: "hi" }). The call is proxied back to the host page, where your handler runs with the validated args.

Common use cases#

  • Theme toggling — generated UI controls your app's appearance
  • Cart / state management — product cards push items into host state
  • Navigation — generated UI triggers route changes in the host app
  • Data fetching — sandbox asks the host to fetch data the iframe can't reach directly

How streaming works#

The agent generates the tool call's parameters in an order optimized for the user experience:

  1. placeholderMessages — shown immediately while generating
  2. css — all styles first; the preview starts once CSS is complete
  3. html — streams live into the preview as it's generated
  4. jsFunctions — reusable helpers injected before expressions
  5. jsExpressions — executed one by one; the user sees each take effect

The middleware parses the tool-call arguments incrementally and emits activity events as each parameter completes, so the preview updates progressively.

Using CDN libraries#

The sandboxed iframe can load external libraries from CDNs; just include <script> or <link> tags in the generated HTML <head>. Chart.js, D3, Three.js, and any other CDN-hosted library work out of the box.

<head>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
  <canvas id="myChart"></canvas>
</body>