---
title: "Dynamic Schema A2UI"
icon: "lucide/Sparkles"
description: "LLM-generated A2UI — a secondary LLM creates both the schema and data from any prompt."
hideTOC: true
snippet_cell: declarative-gen-ui
---

In the dynamic-schema approach, a secondary LLM generates the entire
UI (schema, data, and layout) based on the conversation context.
It's the most flexible A2UI flavor; the agent can render any UI for
any request without pre-defined schemas.

<InlineDemo demo="declarative-gen-ui" />

## How it works

1. The primary LLM decides to call `render_a2ui` (the tool the runtime
   auto-injects when `injectA2UITool: true`).
2. The runtime serializes your client-side catalog (component names +
   Zod prop schemas) into the agent's `copilotkit.context` so the LLM
   knows which components it may emit.
3. The tool call streams through LangGraph as `TOOL_CALL_ARGS` events.
4. The A2UI middleware intercepts the stream and renders cards
   progressively as data items arrive.

## The 3-file split

The canonical Bring-Your-Own-Catalog (BYOC) layout keeps three files
side-by-side under `frontend/src/app/a2ui/`:

| File | What lives there |
|---|---|
| `definitions.ts` | Zod props schema + human-readable descriptions for each custom component. Platform-agnostic, so the runtime can serialise it to the LLM. |
| `renderers.tsx` | React implementations keyed by the same names. TypeScript enforces that every definition has a renderer. |
| `catalog.ts` | `createCatalog(definitions, renderers, { includeBasicCatalog: true })`: merges your custom components with CopilotKit's built-in primitives. |

<Steps>
<Step>
### Declare your custom component definitions

Each entry pairs a Zod prop schema with a description. The description
is crucial; the LLM reads it to decide which component to emit. The
example below ships a small dashboard catalog (Card / StatusBadge /
Metric / InfoRow / PrimaryButton):

<Snippet region="definitions-zod" />
</Step>

<Step>
### Implement the React renderers

Every key in `myDefinitions` must have a matching renderer. Props are
statically typed against the Zod schema, so refactors stay safe:

<Snippet region="renderers-react" />
</Step>

<Step>
### Wire definitions × renderers into a catalog

`createCatalog` is what you hand to the provider. Flip
`includeBasicCatalog: true` to merge CopilotKit's built-ins
(Column, Row, Text, Image, Card, Button, List, Tabs, …), so the LLM
can compose custom + basic components interchangeably:

<Snippet region="create-catalog" />
</Step>

<Step>
### Pass the catalog to the provider

A single prop (`a2ui={{ catalog }}`) is all the frontend needs; the
provider registers the catalog and wires up the built-in A2UI
activity-message renderer:

<Snippet region="provider-a2ui-prop" />
</Step>

<Step>
### Inject the render tool on the runtime

On the TypeScript runtime, `injectA2UITool: true` tells CopilotKit to
add the `render_a2ui` tool to the agent's tool list *at request time*
and serialise your client catalog into the agent's
`copilotkit.context`. No backend code to write; the agent can be an
empty `create_agent(tools=[])`:

```typescript title="app/api/copilotkit/route.ts"
const runtime = new CopilotRuntime({
  agents: { default: myAgent },
  a2ui: {
    injectA2UITool: true,
  },
});
```
</Step>
</Steps>

## Progressive streaming

The secondary LLM's `render_a2ui` tool call streams through LangGraph
as `TOOL_CALL_ARGS` events. The A2UI middleware:

1. Waits for the full `components` array before emitting anything —
   the schema must be complete before rendering starts.
2. Extracts `surfaceId` + `root` from the partial JSON.
3. Emits `surfaceUpdate` + `beginRendering` once the schema is
   complete.
4. Extracts complete `items` objects progressively and emits a
   `dataModelUpdate` for each, so cards appear one by one as data
   streams in.

A built-in progress indicator shows while the schema is still
generating and hides automatically once data items start arriving.

## When should I use dynamic schemas?

- You don't know the UI shape ahead of time; the agent decides what
  to show based on the user's request.
- You want to prototype A2UI without committing to a schema file yet.
- You're building a conversational dashboard where the layout varies
  per turn.

If the surface is well-known (e.g. a product card, a flight result),
prefer a **[fixed schema](./fixed-schema)**; it's faster, cheaper,
and the UI is deterministic.

<IntegrationGrid path="generative-ui/a2ui" />
