Overview
Declarative generative UI is in the middle of the generative UI spectrum. The agent emits a structured specification, and the frontend composes the interface from a catalog of components you register ahead of time. Instead of rendering text responses in chat bubbles, the agent output is the UI: forms, cards, dashboards, and more. You define which components are available (the “catalog”), and the agent composes them into a valid UI tree. The catalog is the guardrail that makes this approach safe: the agent can arrange and combine your components freely, but cannot step outside the set you approve. This balances creativity against predictability. It is where the long tail lives, trading pixel-perfection for breadth, which suits secondary interactions, internal tools, and dashboards where showing something useful matters more than exact control. This page covers declarative generative UI with json-render, a generative UI framework that defines component catalogs, generates specs with AI, and renders them safely across React, Vue, Svelte, and Angular. For Google’s A2UI specification (integrated via CopilotKit), see A2UI below.When to use this approach
Use declarative generative UI for the long tail of your product, where the agent can compose layouts you did not fully anticipate while staying inside a set of components you approve: secondary interactions, internal tools, and dashboards. When a surface is high-traffic or brand-critical and must be exact, move toward controlled generative UI. When you want interfaces created outside your application, move toward open-ended generative UI.How it works
- Define a catalog: declare what components the AI can use, with typed props
- Prompt the AI: describe the UI you want in natural language
- AI generates a spec: a JSON document describing the component tree
- Render safely: json-render’s
Rendererrenders the spec using your components
Define a component catalog
The catalog describes every component the AI is allowed to use. Each component has a Zod schema for its props and a description that the AI reads to understand when to use it:Build a component registry
The registry maps each catalog component to its actual rendering implementation. UsedefineRegistry to get type-safe bindings between the catalog props and
your component functions:
Connect to the agent
The agent uses structured output to return a json-render spec. Set upuseStream
with your agent’s assistant ID, then extract the spec from the AI message’s
tool_calls:
Stream and render progressively
During streaming, the spec is built up incrementally. Elements arrive one at a time and may initially lacktype or props. Filter to only complete elements
and pass loading={true} to the Renderer, which tells it to silently skip
children that haven’t arrived yet. The UI builds up component by component:
The
JSONUIProvider is required to set up json-render’s internal context
providers (state, visibility, validation, actions). The Renderer component
must be rendered inside it.The spec format
The AI agent generates a flat JSON spec with aroot key pointing to the
root element and an elements map containing all components:
TextInput
and Button have empty children arrays.
A2UI: an alternative declarative spec
One way to describe an interface declaratively is json-render. A2UI is another: Google’s declarative, streaming-first generative UI specification, integrated via CopilotKit. Like json-render, it composes interfaces from components you register, so the agent stays inside guardrails you define. A2UI comes in two variants:- Dynamic schema: a secondary model generates the full interface, including schema, data, and layout, from the conversation, for maximum flexibility.
- Fixed schema: the component tree is defined on the frontend and the agent streams only data into it, for the fastest and most predictable rendering.
Best practices
- Use descriptive component descriptions: the AI uses these to understand when to use each component. Clear descriptions lead to better UI generation.
- Validate before rendering: always check that elements have valid
typeand non-nullpropsbefore passing to the Renderer, since streaming delivers partial data. - Design for streaming: pass
loading={true}during streaming so the Renderer gracefully handles children that haven’t arrived yet. Users see the UI build up in real time rather than waiting for the full response. - Style with design tokens: use CSS custom properties so rendered components adapt to light and dark themes automatically.
- Wrap with JSONUIProvider: the
Renderermust be inside aJSONUIProviderto access json-render’s internal context for state, visibility, and actions.
See also
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

