Skip to main content

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

  1. Define a catalog: declare what components the AI can use, with typed props
  2. Prompt the AI: describe the UI you want in natural language
  3. AI generates a spec: a JSON document describing the component tree
  4. Render safely: json-render’s Renderer renders the spec using your components
The catalog acts as a guardrail: the AI can only use components you’ve defined, with props that match your schema. The output is always predictable and safe.

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:
Keep catalogs focused. Include only components the AI needs for the use case. A smaller catalog produces better results than a kitchen-sink approach.

Build a component registry

The registry maps each catalog component to its actual rendering implementation. Use defineRegistry 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 up useStream 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 lack type 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 a root key pointing to the root element and an elements map containing all components:
Each element references its children by ID, and leaf elements like 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.
For details, see CopilotKit’s docs on A2UI, dynamic schema, and fixed schema. To wire CopilotKit to a LangGraph deployment, see CopilotKit.

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 type and non-null props before 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 Renderer must be inside a JSONUIProvider to access json-render’s internal context for state, visibility, and actions.

See also