showcase/shell-docs/src/content/docs/generative-ui/a2ui/dynamic-schema.mdx
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" />render_a2ui (the tool the runtime
auto-injects when injectA2UITool: true).copilotkit.context so the LLM
knows which components it may emit.TOOL_CALL_ARGS events.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. |
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 renderersEvery key in myDefinitions must have a matching renderer. Props are
statically typed against the Zod schema, so refactors stay safe:
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:
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:
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=[]):
const runtime = new CopilotRuntime({
agents: { default: myAgent },
a2ui: {
injectA2UITool: true,
},
});
The secondary LLM's render_a2ui tool call streams through LangGraph
as TOOL_CALL_ARGS events. The A2UI middleware:
components array before emitting anything —
the schema must be complete before rendering starts.surfaceId + root from the partial JSON.surfaceUpdate + beginRendering once the schema is
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.
If the surface is well-known (e.g. a product card, a flight result), prefer a fixed schema; it's faster, cheaper, and the UI is deterministic.
<IntegrationGrid path="generative-ui/a2ui" />