Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/a2ui-project/a2ui/llms.txt

Use this file to discover all available pages before exploring further.

A2UI is built on three interlocking ideas that together allow AI agents to produce rich, interactive UIs safely and portably. This page explains those ideas from the ground up: how the architecture is layered, what message types agents send, how components are structured, how data flows reactively through a surface, what catalogs are and why they matter for security, and how A2UI messages travel over different transports. Understanding these concepts will make every other part of the documentation click into place.

The Three-Layer Architecture

Every A2UI interaction is organized into three distinct layers:
┌───────────────────────────────────────────────┐
│  Layer 1 — Streaming Messages                 │
│  JSON objects sent from agent to client        │
│  (createSurface, updateComponents, …)          │
├───────────────────────────────────────────────┤
│  Layer 2 — Declarative Components             │
│  Flat adjacency-list describing the UI tree   │
│  (Button, TextField, Card, Column, …)          │
├───────────────────────────────────────────────┤
│  Layer 3 — Data Binding                       │
│  Reactive JSON Pointer paths connect          │
│  components to mutable application state      │
└───────────────────────────────────────────────┘
Why three layers? Separating message transport, UI structure, and application state means each layer can change independently. The agent can update the data model without regenerating the component tree; the client can restyle a surface without the agent knowing; and the same component tree can render across platforms by swapping only the renderer.

Message Types

All A2UI communication is a sequence of self-contained JSON messages (streamed as JSON Lines / JSONL). The set of message types differs slightly between protocol versions:
MessagePurpose
createSurfaceCreate a new surface and declare which catalog the agent will use
updateComponentsAdd or update UI components in a surface
updateDataModelUpdate application state at a specific JSON Pointer path
deleteSurfaceRemove a surface and release its resources
All v0.9 messages include a top-level version field. createSurface replaces the old beginRendering — surface creation and render initiation are now a single, explicit step.
{ "version": "v0.9.1", "createSurface": {
    "surfaceId": "booking",
    "catalogId": "https://a2ui.org/specification/v0_9_1/catalogs/basic/catalog.json"
}}
{ "version": "v0.9.1", "updateComponents": {
    "surfaceId": "booking",
    "components": [
      { "id": "header", "component": "Text", "text": "Book Your Table", "variant": "h1" },
      { "id": "date",   "component": "DateTimeInput", "value": { "path": "/booking/date" }, "enableDate": true }
    ]
}}
{ "version": "v0.9.1", "updateDataModel": {
    "surfaceId": "booking",
    "path": "/booking",
    "value": { "date": "2025-12-16T19:00:00Z" }
}}
For complete field-level documentation of every message type, see the Message Reference.

Surfaces

A surface is a named, isolated area of UI that an agent creates and manages. Think of it as a canvas: a dialog, a sidebar panel, a main content area, or a card embedded in a chat stream. Each surface has its own:
  • surfaceId — a string that uniquely identifies the surface; every message that affects a surface must include this ID.
  • Component tree — the flat list of components that make up the surface.
  • Data model — an independent JSON object holding that surface’s application state.
Surfaces are flat — they cannot nest inside one another. An orchestrator managing multiple agents can display several surfaces simultaneously, each scoped to a different surfaceId, without any risk of state collision.
When the agent sends deleteSurface, the client removes the surface and its data model from memory. This is the canonical signal that a workflow step is complete.

Components: The Adjacency List Model

A2UI represents component hierarchies as a flat list of nodes with ID references — an adjacency list — rather than a nested JSON tree. This is a deliberate design choice.

Why flat lists beat nested trees

Nested treeAdjacency list (A2UI)
LLM generationMust track indentation depth across the whole responseEach component is an independent, flat object
StreamingHard to render partial output; tree isn’t valid until closedAny individual component can be rendered as soon as it arrives
Incremental updateReplacing one node requires resending its entire subtreeUpdate any node by id; the rest is untouched
Error correctionMalformed nesting can break the entire payloadA malformed component doesn’t affect its siblings

Component anatomy

Every component has three required fields:
  1. id — a unique string identifier within the surface (use descriptive names like "user-profile-card", not "c1").
  2. component — the component type string, matching a name in the active catalog ("Text", "Button", "DateTimeInput", etc.).
  3. Properties — zero or more type-specific fields, either as literal values or reactive data-binding paths.
A flat component list using the v0.9 format. Parent–child relationships are expressed as arrays of child IDs:
{
  "version": "v0.9.1",
  "updateComponents": {
    "surfaceId": "main",
    "components": [
      { "id": "root",        "component": "Column",  "children": ["greeting", "buttons"] },
      { "id": "greeting",    "component": "Text",    "text": "Hello!" },
      { "id": "buttons",     "component": "Row",     "children": ["cancel-btn", "ok-btn"] },
      { "id": "cancel-btn",  "component": "Button",  "child": "cancel-text", "action": { "event": { "name": "cancel" } } },
      { "id": "cancel-text", "component": "Text",    "text": "Cancel" },
      { "id": "ok-btn",      "component": "Button",  "child": "ok-text",     "action": { "event": { "name": "ok" } } },
      { "id": "ok-text",     "component": "Text",    "text": "OK" }
    ]
  }
}

Static vs. dynamic children

Children can be a fixed list ("children": ["a", "b"]) or a template that generates one child instance per item in a data array:
{
  "id": "product-list",
  "component": "Column",
  "children": {
    "path": "/products",
    "componentId": "product-card"
  }
}
For every item in the /products array, the renderer instantiates product-card with that item’s data in scope. Adding or removing items from /products automatically adds or removes rendered cards.
For the full component catalog with properties and live examples, see the Component Reference.

Data Binding

A2UI separates UI structure (what the interface looks like) from application state (what data it displays). Components connect to state via JSON Pointer paths (RFC 6901).

The data model

Each surface maintains an independent JSON object as its data model:
{
  "user":       { "name": "Alice", "email": "alice@example.com" },
  "reservation": { "date": "2025-12-16T19:00:00Z", "guests": 2 },
  "cart":        { "items": [{ "name": "Widget", "price": 9.99 }], "total": 9.99 }
}
JSON Pointer path syntax:
PathResolves to
/user/name"Alice"
/reservation/guests2
/cart/items/0/name"Widget"

Reactive binding

When a component’s property references a path, it updates automatically whenever that path changes — no component update message is needed:
{ "id": "status-text", "component": "Text", "text": { "path": "/order/status" } }
When the agent sends:
{ "version": "v0.9.1", "updateDataModel": {
    "surfaceId": "main",
    "path": "/order/status",
    "value": "Shipped"
}}
The status-text component re-renders with “Shipped” — automatically, without any updateComponents message.

Bidirectional binding for inputs

Input components (TextField, CheckBox, ChoicePicker, DateTimeInput) write user interactions back to the data model automatically. The agent can read the current value of any bound path when the user triggers an action:
Component (v0.9)Binding exampleUser actionData model update
TextField"value": { "path": "/form/name" }Types “Alice”/form/name"Alice"
CheckBox"value": { "path": "/form/agreed" }Checks box/form/agreedtrue
ChoicePicker"value": { "path": "/form/country" }Selects “Canada”/form/country["ca"]
For full data binding syntax and advanced patterns (scoped template paths, nested objects), see the Data Binding Reference.

Catalogs

A catalog is a JSON Schema file that defines exactly which component types, properties, functions, and theme values the agent is permitted to use on a given surface. It is the primary security boundary in A2UI.

Why catalogs matter

Without a catalog, an agent could request arbitrary components — including ones the client doesn’t implement, ones that conflict with the host app’s design system, or ones that expose unintended behaviors. With a catalog:
  • The client is in control. The agent can only request what the client has pre-approved and implemented.
  • Payloads are validated. Both the agent (pre-send) and client (on-receive) validate every A2UI message against the catalog schema.
  • Design consistency is enforced. A production catalog mirrors the host app’s design system — PrimaryButton, HeroCard, DataGrid — not generic primitives.

The Basic Catalog

For getting started quickly, the A2UI team maintains a Basic Catalog — a pre-defined schema with general-purpose primitives (Button, TextField, Card, Column, Row, Text, DateTimeInput, etc.). It has open-source renderers for Lit, Angular, and React. Reference it in createSurface to use the Basic Catalog:
{
  "version": "v0.9.1",
  "createSurface": {
    "surfaceId": "my-surface",
    "catalogId": "https://a2ui.org/specification/v0_9_1/catalogs/basic/catalog.json"
  }
}

Catalog negotiation

Because clients and agents may support different catalogs, A2UI defines a negotiation handshake:
1

Client advertises supported catalogs

The client sends a prioritized list of supportedCatalogIds in the metadata of every message it sends to the agent:
{
  "parts": [{ "text": "Book a table for 2" }],
  "metadata": {
    "a2uiClientCapabilities": {
      "supportedCatalogIds": [
        "https://example.com/catalogs/my-app/v2/catalog.json",
        "https://a2ui.org/specification/v0_9_1/catalogs/basic/catalog.json"
      ]
    }
  }
}
2

Agent selects the best match

When creating a surface, the agent picks the highest-priority catalog from the client’s list that it is capable of generating. This choice is locked for the lifetime of that surface. If no compatible catalog is found, the agent sends no UI.
3

Client validates and renders

The client validates the incoming A2UI payload against its local copy of the selected catalog. Validation errors are reported back to the agent using a VALIDATION_FAILED error message so the agent can self-correct.

Custom catalogs

Most production applications define their own catalog that matches their design system. A minimal custom catalog looks like this:
{
  "$id": "https://example.com/catalogs/my-app/v1/catalog.json",
  "components": {
    "HeroCard": {
      "type": "object",
      "description": "A large featured card with image and CTA.",
      "properties": {
        "title":    { "type": "string" },
        "subtitle": { "type": "string" },
        "imageUrl": { "type": "string" },
        "ctaLabel": { "type": "string" }
      },
      "required": ["title", "ctaLabel"]
    }
  }
}
The catalogId URI is used purely as a stable, human-readable identifier for negotiation — not for runtime fetching. Both agent and client must have the catalog definition available at build/deploy time.
For catalog versioning, graceful degradation, and the full linking workflow, see the Catalogs Reference.

Transports

A2UI is transport-agnostic. Any mechanism that can carry JSON messages from agent to client works. The protocol defines the message format; the transport is the pipe.
TransportBest for
A2A ProtocolMulti-agent systems and remote agents; standardized agent-to-agent communication
AG-UI (CopilotKit)Bidirectional real-time agent–UI communication with state sync; use AG-UI as the pipe, A2UI as the payload
Server-Sent Events (SSE)One-way streaming over HTTP; simple and broadly supported
WebSocketPersistent bidirectional connections; ideal when the client also needs to send frequent updates
REST / HTTPSimple request-response for non-streaming use cases
CustomgRPC, message queues, in-process — if it carries JSON, it works
The restaurant finder demo uses the A2A Protocol: the Python agent exposes an A2A endpoint and the Lit client connects to it. For CopilotKit-based setups, AG-UI carries the A2UI messages transparently.
For implementation details on each transport adapter, see the Transports Reference.

Putting It All Together

Here is a complete lifecycle for a restaurant booking request, showing all three layers working together:
  1. Agent creates the surface (createSurface) — declares the catalog; client allocates a new surface context.
  2. Agent defines the UI (updateComponents) — streams a flat list of component records; client renders them progressively.
  3. Agent populates state (updateDataModel) — sends initial field values; bound components update automatically.
  4. User edits a field — the TextField writes the new value back to the data model; no agent message needed.
  5. User clicks Submit — the Button fires an action event with the current data model snapshot; the agent receives it.
  6. Agent confirms — either updates the surface to show a confirmation state or sends deleteSurface to close it.

Concept Reference Cards

Message Reference

Full field-level specification for all message types across v0.8 and v0.9.

Component Reference

Every component in the Basic Catalog with properties, examples, and rendering notes.

Data Binding Reference

JSON Pointer path syntax, reactive binding patterns, input bindings, and dynamic lists.

Catalogs Reference

Catalog schema, negotiation protocol, versioning strategy, and custom catalog guide.

Transports Reference

A2A, AG-UI, SSE, WebSocket, and custom transport integration details.

Glossary

Short definitions for all A2UI protocol terms: surface, catalog, data model, renderer, agent turn, and more.

Build docs developers (and LLMs) love