Technical Documentation: a2ui-project/a2ui
βΉοΈ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference
Overview (README.md)
A2UI: Agent-to-User Interface
A2UI is an open-source project, complete with a format
optimized for representing updatable agent-generated
UIs and an initial set of renderers, that allows agents
to generate or populate rich user interfaces.
A gallery of A2UI rendered cards, showing a variety of UI compositions that A2UI can achieve.
β οΈ Status: Early stage public preview
Note: A2UI's current production release is v0.9.1, a patch release in
the stable v0.9 protocol family. The v1.0 specification is a release
candidate, while v0.8 is legacy. The specification and implementations are
functional but are still evolving. We are opening the project to foster
collaboration, gather feedback, and solicit contributions (e.g., on client
renderers). Expect changes.
Summary
Generative AI excels at creating text and code, but agents can struggle to
present rich, interactive interfaces to users, especially when those agents
are remote or running across trust boundaries.
A2UI is an open standard and set of libraries that allows agents to
"speak UI." Agents send a declarative JSON format describing the intent of
the UI. The client application then renders this using its own native
component library (Flutter, Angular, Lit, etc.).
This approach ensures that agent-generated UIs are
safe like data, but expressive like code.
High-level philosophy
A2UI was designed to address the specific challenges of interoperable,
cross-platform, generative or template-based UI responses from agents.
The project's core philosophies:
- Security first: Running arbitrary code generated by an LLM may present a
security risk. A2UI is a declarative data format, not executable
code. Your client application maintains a "catalog" of trusted, pre-approved
UI components (e.g., Card, Button, TextField), and the agent can only request
to render components from that catalog. - LLM-friendly and incrementally updatable: The UI is represented as a flat
list of components with ID references which is easy for LLMs to generate
incrementally, allowing for progressive rendering and a responsive user
experience. An agent can efficiently make incremental changes to the UI based
on new user requests as the conversation progresses. - Framework-agnostic and portable: A2UI separates the UI structure from
the UI implementation. The agent sends a description of the component tree
and its associated data model. Your client application is responsible for
mapping these abstract descriptions to its native widgetsβbe it web components,
Flutter widgets, React components, SwiftUI views or something else entirely.
The same A2UI JSON payload from an agent can be rendered on multiple different
clients built on top of different frameworks. - Flexibility: A2UI also features an open registry pattern that allows
developers to map server-side types to custom client implementations, from
native mobile widgets to React components. By registering a "Smart Wrapper,"
you can connect any existing UI componentβincluding secure iframe containers
for legacy contentβto A2UI's data binding and event system. Crucially, this
places security firmly in the developer's hands, enabling them to enforce
strict sandboxing policies and "trust ladders" directly within their custom
component logic rather than relying solely on the core system.
Use cases
Some of the use cases include:
- Dynamic Data Collection: An agent generates a bespoke form (date pickers,
sliders, inputs) based on the specific context of a conversation (e.g.,
booking a specialized reservation). - Remote Sub-Agents: An orchestrator agent delegates a task to a
remote specialized agent (e.g., a travel booking agent) which returns a
UI payload to be rendered inside the main chat window. - Adaptive Workflows: Enterprise agents that generate approval
dashboards or data visualizations on the fly based on the user's query.
Architecture
The A2UI flow disconnects the generation of UI from the execution of UI:
- Generation: An Agent (using Gemini or another LLM) generates or uses
a pre-generatedA2UI Response, a JSON payload describing the composition
of UI components and their properties. - Transport: This message is sent to the client application
(via A2A, AG-UI, etc.). - Resolution: The Client's A2UI Renderer parses the JSON.
- Rendering: The Renderer maps the abstract components
(e.g.,type: 'text-field') to the concrete implementation in the client's codebase.
Dependencies
A2UI is designed to be a lightweight format, but it fits into a larger ecosystem:
- Transports: Compatible with A2A Protocol and AG-UI.
- LLMs: Can be generated by any model capable of generating JSON output.
- Host Frameworks: Requires a host application built in a supported framework
(currently: Web or Flutter).
Getting started
Pick the path that matches where you want to start:
| Path | What you get | Time |
|---|---|---|
| π Quickstart Restaurant Finder Demo | Full-stack A2UI running locally with a Gemini powered ADK agent and Lit renderer. Learn A2UI end-to-end and customize to your use case. | ~5 min |
| βοΈ Use A2UI with Any Agent Framework & Harness | Scaffold an AG-UI app or harness for your framework of choice, then enable A2UI rendering over AG-UI. | ~5 min |
| π¨ A2UI Composer | Generate A2UI JSON from a visual editor and paste it into any agent prompt β no install required. | ~1 min |
| π¬ A2UI Theater | Step through pre-built A2UI streaming scenarios across Lit, React, and Angular renderers β no install required. | ~1 min |
Restaurant Finder demo β Summary
Prerequisites: Node.js 18+ (with Corepack enabled), uv, and a Gemini API key.
git clone https://github.com/a2ui-project/a2ui.git
cd a2ui
export GEMINI_API_KEY="your_gemini_api_key"
# Enable Corepack (macOS Homebrew users: see tip below)
corepack enable
yarn install
cd samples/client/lit
yarn demo:restaurant[!TIP]
macOS Homebrew Users: If you previously installed standalone package managers, unlink conflicts before installing Corepack so Corepack can manage versions per-project:bashbrew unlink yarn pnpm brew install corepack corepack enable
These commands install dependencies across workspaces, build the renderers, start the Python agent, and open the client at http://localhost:5173. For step-by-step instructions, alternative demos, and troubleshooting see the full Quickstart.
Use A2UI with Any Agent Framework & Harness β Summary
npx create-ag-ui-app@latestUse the AG-UI CLI with your framework or harness of choice (Google Chat, ADK, LangGraph, CrewAI, Mastra, Strands, Slack, Teams, etc.), then follow the AG-UI guide to enable A2UI rendering. Some scaffold paths use CopilotKit's A2UI runtime with Next.js under the hood, but the setup surface is AG-UI-first.
Other renderers
For Flutter, check out the GenUI SDK, which uses A2UI under the hood. See docs/public/reference/renderers.md for the full list of client implementations.
Roadmap
We hope to work with the community on the following:
- Spec stabilization: Moving towards a v1.0 specification.
- More renderers: Adding official support for React, Jetpack Compose, iOS (SwiftUI), and more.
- Additional transports: Support for REST and more.
- Additional Agent frameworks: Genkit, LangGraph, and more.
Contribute
A2UI is an Apache 2.0 licensed project. We believe the future of UI is agentic,
and we want to work with you to help build it.
See CONTRIBUTING.md for details on how to get started.
Public Index (`docs/public/index.md`)
hide:
- toc
A Protocol for Agent-Driven Interfaces
A2UI enables AI agents to generate rich, interactive user interfaces that render natively across web, mobile, and desktopβwithout executing arbitrary code.
Specification Versions
| Version | Status | Description |
|---|---|---|
| v1.0 | Candidate | Release candidate. Adds client-to-server RPC (actionResponse), action IDs, and renames theme to surfaceProperties. (Previously designated as v0.10 when in draft). Evolution guide β |
| v0.9.1 | Current | Current production release. Minor refinements to v0.9, standardizing on the application/a2ui+json MIME type and relaxing surfaceId constraints. Evolution guide β |
| v0.9 | Stable | Previous stable version. Philosophical shift to Prompt-First. Introduces createSurface, client-side functions, custom catalogs, modular schemas, and validation failed error formatting. Evolution guide β |
| v0.8 | Legacy | Legacy version. Structured Output first. Baseline surfaces, components, data binding, and adjacency list model. |
A2UI is Apache 2.0 licensed,
created by Google with contributions from
CopilotKit and the open source community,
and is in active development on GitHub.
A2UI solves the following problem: how can AI agents safely send rich UIs across trust boundaries?
Instead of text-only responses or risky code execution, A2UI lets agents send declarative component descriptions that clients render using their own native widgets. It's like having agents speak a universal UI language.
This repository contains:
A2UI specifications (v0.9.1 current, v1.0 candidate).
Implementations for renderers (Angular, Flutter, Lit, Markdown, etc.) on the client side.
Transports like A2A which communicate A2UI messages between agents and clients.
:material-shield-check: Secure by Design
Declarative data format, not executable code. Agents can only use pre-approved components from your catalogβno UI injection attacks.
:material-rocket-launch: LLM-Friendly
Flat, streaming JSON structure designed for easy generation. LLMs can build UIs incrementally without perfect JSON in one shot.
:material-devices: Framework-Agnostic
One agent response works everywhere. Render the same UI on Angular, Flutter, React, or native mobile with your own styled components.
:material-chart-timeline: Progressive Rendering
Stream UI updates as they're generated. Users see the interface building in real-time instead of waiting for complete responses.
Get Started in 5 Minutes
:material-clock-fast:{ .lg .middle } Quickstart Restaurant Finder Demo
Run the full-stack demo locally with a Gemini powered ADK agent and Lit renderer. Learn A2UI end-to-end and customize to your use case.
:material-react:{ .lg .middle } Use A2UI with Any Agent Framework & Harness
Scaffold an AG-UI app or harness for your agent framework, then enable A2UI rendering in the client surface.
:material-palette-outline:{ .lg .middle } A2UI Composer
Generate A2UI JSON from a visual editor β no install required. Paste the output into any agent prompt.
:material-book-open-variant:{ .lg .middle } Core Concepts
Understand surfaces, components, data binding, and the adjacency list model.
:material-code-braces:{ .lg .middle } Developer Guides
Integrate A2UI renderers into your app or build agents that generate UIs.
:material-file-document:{ .lg .middle } Protocol Specifications
Dive into the complete technical specs: v0.8 (legacy) Β· v0.9 (stable) Β· v0.9.1 (current) Β· v1.0 (candidate)
How It Works
The typical interaction flow consists of these steps:
- User sends a message to an AI agent
- Agent generates A2UI messages describing the UI (structure + data)
- Messages stream to the client application
- Client renders using native components (Angular, Flutter, React, etc.)
- User interacts with the UI, sending actions back to the agent
- Agent responds with updated A2UI messages
A2UI in Action
Landscape Architect Demo
<video width="100%" height="auto" controls playsinline style="display: block; aspect-ratio: 16/9; object-fit: cover;">
Your browser does not support the video tag.
</video>
Watch an agent generate all of the interfaces for a landscape architect application. The user uploads a photo; the agent uses Gemini to understand it and generate a custom form for landscaping needs.Custom Components: Interactive Charts & Maps
<video width="100%" height="auto" controls playsinline style="display: block; aspect-ratio: 16/9; object-fit: cover;">
Your browser does not support the video tag.
</video>
Watch an agent chose to respond with a chart component to answer a numerical summary question. Then the agent chooses a Google Map component to answer a location question. Both are custom components offered by the client.A2UI Composer
We have a public A2UI Composer.
Documentation for the A2UI Composer
Public Quickstart (`docs/public/quickstart.md`)
Quickstart: Run A2UI in 5 Minutes
Get hands-on with A2UI by running the restaurant finder demo. This guide will have you experiencing agent-generated UI in less than 5 minutes.
What You'll Build
By the end of this quickstart, you'll have:
- A running app with an A2UI renderer (Lit or Flutter).
- A Gemini-powered agent that generates dynamic UIs.
- An interactive restaurant finder with form generation, time selection, and confirmation flows.
- Understanding of how A2UI messages flow from agent to UI.
Prerequisites
Before you begin, choose your client framework:
=== "Lit"
- **Node.js** (v18 or later with [Corepack](https://nodejs.org/api/corepack.html) enabled) β [Download here](https://nodejs.org/)=== "Flutter"
- **Flutter SDK** β [Install here](https://docs.flutter.dev/install)Common Prerequisites:
- uv (Python package manager) β Install here (used to run the Python agent backend)
- A Gemini API key β Get one free from Google AI Studio
[!WARNING]
Security NoticeThis demo runs an A2A agent that uses Gemini to generate A2UI responses. The agent has access to your API key and will make requests to Google's Gemini API. Always review agent code before running it in production environments.
Step 1: Clone the Repository
git clone https://github.com/a2ui-project/a2ui.git
cd a2uiStep 2: Set Your API Key
Export your Gemini API key as an environment variable:
export GEMINI_API_KEY="your_gemini_api_key_here"Step 3: Run the Agent and Client
=== "Lit"
The client application source code is located in `samples/client/lit/shell`. Navigate to the parent samples directory to run the demo:
```bash
cd samples/client/lit
# Enable Corepack (macOS Homebrew users: see tip below)
corepack enable
yarn install
yarn demo:restaurant
```
??? note "Running the agent and client separately"
The `demo:restaurant` command runs both the A2A agent and the web client
automatically. It is equivalent to:
**1. Run the Agent:**
```bash
cd samples/agent/adk/restaurant_finder
uv run .
```
**2. Run the Client:**
```bash
cd samples/client/lit/shell
yarn dev
```
> [!INFO]
> **About Package Managers:**
>
> The A2UI repository uses `yarn` for its development, but `yarn` is not
> required to **use** A2UI. You may use the package manager of your choice
> on your projects (e.g. `npm` or `pnpm`).=== "Flutter"
**1. Run the Agent:**
In your first terminal, start the Python agent backend:
```bash
cd samples/agent/adk/restaurant_finder
uv run .
```
**2. Run the Client:**
In a second terminal, launch the Flutter web application:
```bash
cd samples/client/flutter/restaurant_finder/app
flutter run -d chrome
```[!TIP]
Demo RunningIf everything worked, you should see the demo app. The agent is now ready to
generate UI!
Step 4: Try It Out
In the web app, try these prompts:
- "Book a table for 2" - Watch the agent generate a reservation form
- "Find Italian restaurants near me" - See dynamic search results
- "What are your hours?" - Experience different UI layouts for different intents
The demo application should update after each response from the agent, with UI
completely generated by the Gemini LLM. The resulting screens (list of restaurants,
reservation flow, reservation confirmation...) are not hardcoded in the source
of the app.
How does it work?
Let's dive deeper into how this demo app is put together, and how you can build similar applications with A2UI.
Interaction sequence diagram
sequenceDiagram
actor User
box Front-end
participant App@{"alias":"App (A2UI renderer)"}
end
box Back-end
participant Agent@{"alias":"A2A agent (Python)"}
participant LLM@{"alias":"Gemini API (LLM)"}
end
User->>App: Interacts
App->>Agent: ClientToServerMessage
activate Agent
Agent->>LLM: Query LLM
activate LLM
LLM-->>Agent: Generate A2UI JSON payload
deactivate LLM
Agent-->>App: (Streaming) JSONL messages
deactivate Agent
App-->>User: Updated UI- User interacts with your app (sends a message, clicks a button, etc.)
- The A2A agent receives it and sends the conversation to Gemini
- Gemini generates A2UI JSON messages describing the UI
- The A2A agent streams these messages back to the app
- The A2UI renderer converts them into UI components
- Your app UI updates
The A2UI JSON payloads
Let's peek at what the agent is returning. Here's a simplified example of the JSON messages:
=== "v0.9"
**Creating the surface:**
```json
{ "version": "v0.9.1",
"createSurface": {
"surfaceId": "main",
"catalogId": "https://a2ui.org/specification/v0_9_1/catalogs/basic/catalog.json"
}}
```
**Defining the UI:**
```json
{ "version": "v0.9.1",
"updateComponents": {
"surfaceId": "main",
"components": [
{"id": "header", "component": "Text", "text": "# Book Your Table", "variant": "h1"},
{"id": "date-picker", "component": "DateTimeInput", "label": "Select Date", "value": {"path": "/reservation/date"}, "enableDate": true},
{"id": "submit-text", "component": "Text", "text": "Confirm Reservation"},
{"id": "submit-btn", "component": "Button", "child": "submit-text", "variant": "primary", "action": {"event": {"name": "confirm_booking"}}}
]
}}
```
**Populating data:**
```json
{ "version": "v0.9.1",
"updateDataModel": {
"surfaceId": "main",
"path": "/reservation",
"value": {"date": "2025-12-15", "time": "19:00", "guests": 2}
}}
```=== "Legacy (v0.8)"
**Defining the UI:**
```json
{"surfaceUpdate": {"surfaceId": "main", "components": [
{"id": "header", "component": {"Text": {"text": {"literalString": "Book Your Table"}, "usageHint": "h1"}}},
{"id": "date-picker", "component": {"DateTimeInput": {"label": {"literalString": "Select Date"}, "value": {"path": "/reservation/date"}, "enableDate": true}}},
{"id": "submit-text", "component": {"Text": {"text": {"literalString": "Confirm Reservation"}}}},
{"id": "submit-btn", "component": {"Button": {"child": "submit-text", "action": {"name": "confirm_booking"}}}}
]}}
```
**Populating data:**
```json
{"dataModelUpdate": {"surfaceId": "main", "contents": [
{"key": "reservation", "valueMap": [
{"key": "date", "valueString": "2025-12-15"},
{"key": "time", "valueString": "19:00"},
{"key": "guests", "valueInt": 2}
]}
]}}
```
**Signaling render:**
```json
{"beginRendering": {"surfaceId": "main", "root": "header"}}
```
Note: In v0.8, `createSurface` was `beginRendering`, components were nested
in the response, and the data model used an adjacency list instead of
flat JSON.[!TIP]
It's Just JSONNotice how readable and structured this is? LLMs can generate this easily, and it's safe to transmit and renderβno code execution required.
Peek at the source code
Want to see what the code looks like? Check out:
- Agent Code:
samples/agent/adk/restaurant_finder/β The Python A2A agent
=== "Lit"
- **Client Code**: `samples/client/lit/` β The Lit web client with A2UI renderer
- **A2UI Renderers**: `typescript/renderers/lit/` (Lit) and `typescript/core/web_core/` (framework-agnostic core)=== "Flutter"
- **Client Code**: `samples/client/flutter/` β The Flutter web client with A2UI renderer
- **A2UI Renderer**: `dart/a2ui_flutter/` (Flutter)Each directory has its own README with detailed documentation.
Troubleshooting
=== "Lit"
### Port Already in Use
If port 5173 is already in use, the dev server will automatically try the next available port. Check the terminal output for the actual URL.
### Corepack and Homebrew issues
If you have standalone package managers installed, try to unlink conflicts before installing Corepack so Corepack can manage versions per-project:
> ```bash
> $ brew unlink yarn pnpm
> $ brew install corepack
> $ corepack enable
> ```=== "Flutter"
API Key Issues
If you see errors about missing API keys:
- Verify the key is exported:
echo $GEMINI_API_KEY - Make sure it's a valid Gemini API key from Google AI Studio
- Try re-exporting:
export GEMINI_API_KEY="your_key"
Connection Errors on Startup
If you see ERR_CONNECTION_REFUSED errors when the browser opens, don't worry β this is a known race condition. The web app may start faster than the Python agent backend. Just wait a few seconds and refresh the page.
Python / uv Issues
The demo agents require uv to run. If you see uv: command not found:
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Verify
uv --versionIf you encounter other Python errors:
# Make sure Python 3.10+ is available
python3 --version
# Try running the agent manually
cd samples/agent/adk/restaurant_finder
uv run .Still Having Issues?
- Check the GitHub Issues
- Review the sample README.md (Lit or Flutter)
- Join the community discussions
Exploring more demos
=== "Lit"
### Lit Component Gallery (No Agent Required)
See all the provided Basic Catalog components:
If you're running the gallery from a fresh checkout, build the gallery and its workspace dependencies first:
```bash
cd typescript/renderers/lit/a2ui_explorer
yarn build
```
Start the gallery:
```bash
yarn dev
```
This runs a client-only demo showcasing every standard component (Card, Button, TextField, Timeline, etc.) with live examples and code samples.=== "Flutter"
Other Languages and Frameworks
A2UI provides samples for other popular frameworks in the samples/client directory:
- Angular:
samples/client/angular - Flutter:
samples/client/flutter - Lit:
samples/client/lit - React:
samples/client/react
Explore the samples/client directory to see all available client implementations.
What's Next?
Congratulations! You've successfully run your first A2UI application. You've seen how an AI agent can generate rich, interactive UIs that render natively in a web applicationβall through safe, declarative JSON messages.
Check out the following links to learn more:
- Learn Core Concepts: Understand surfaces, components, and data binding
- Build an Agent: Create agents that generate A2UI responses
- Set Up Your Own Client: Integrate A2UI into your own app
- Define Your Own Catalog: Move past the Basic Catalog, and control the UI elements used to generate your app
- Use an Existing Agent App: Add A2UI through CopilotKit + AG-UI for ADK, LangGraph, CrewAI, Mastra, or a custom service
- Explore the Protocol: Dive into the technical specification
Scripts README (`docs/scripts/README.md`)
Documentation Transformation Scripts
This directory contains utility scripts to prepare our documentation for the MkDocs build process.
Purpose
To ensure a great reading experience both on GitHub and the hosted site, the project uses GitHub-flavored Markdown as the primary source of truth. This script transforms GitHub's native syntax into MkDocs-compatible syntax (specifically for the pymdown-extensions) during the build pipeline.
Supported Conversions (Uni-directional)
The script performs a uni-directional transformation: GitHub Markdown β MkDocs Syntax.
Alert/Admonition Conversion
The script handles the following conversions:
- GitHub uses a blockquote-based syntax for alerts.
- MkDocs requires the
!!!or???syntax to render colored callout boxes.
Running the Conversion
The conversion is run as part of the build pipeline. No additional steps are required. If you need to run the conversion manually, you can run the convert_docs.py script in the repository root.
python docs/scripts/convert_docs.pyExample
Source (GitHub-flavored Markdown):
markdown> β οΈ **Attention** > > This is an alert.Target (MkDocs Syntax):
markdown!!! warning "Attention" This is an alert.
Public Composer Index (`docs/public/composer/index.md`)
A2UI Composer
Try building A2UI widgets interactively with the A2UI Composer.
Navigate to the A2UI Composer
By default, you'll be using the Basic catalog provided by an Angular-based
rendererUse the Gemini chat to start building an A2UI interface! (Note: This
requires a Gemini API key. See below)
Using the Composer UI
The Composer workspace is split into several interactive panels to assist in
developing and debugging A2UI surfaces:
Gemini Assistant: A chat interface powered by Gemini. You can use
natural language prompts to ask Gemini to generate new layouts, refine the
existing JSON, or modify visual attributes.You can upload an attachment (a mock, for example) by clicking the
paper clip icon
{style="width:30px;height:30px;display:inline;vertical-align:middle;"}.You can include a screenshot of the
current A2UI interface by clicking the camera icon
{style="width:30px;height:30px;display:inline;vertical-align:middle;"}.Rendered A2UI Preview: Displays the live visual preview of the active
component.A2UI JSON Editor: Displays the raw JSON payload defining the UI
structure and component hierarchy. You can edit this directly to instantly
update the rendered preview.The editor has tooltips on hover,
showing the description of the A2UI element.If you have A2UI JSON from another source, you can paste it into this
panel. You can right-click to format the JSON if you want.Debug & Inspection Tabs (Bottom):
- Data Model: Inspects and modifies the runtime state/data values
bound to the UI components. Changes here propagate to the preview, and
user inputs in the preview update this model. - Events: Logs user-interaction events (e.g., clicks, inputs, or
selections) dispatched by the rendered components. - Errors: Displays errors from rendering, JSON parsing, or API
failures. - Raw Messages: Shows the communication between the Composer and the
renderer, as well as interactions with Gemini. (See more below.)
- Data Model: Inspects and modifies the runtime state/data values
Components Gallery
The Components gallery allows you to browse through all the components which
are provided in the current A2UI catalog. Each component has an example
rendered, along with the A2UI JSON to produce that rendering. Use the copy icon
{style="width:30px;height:30px;display:inline;vertical-align:middle;"}
at the top of the Usage panel to copy the full A2UI JSON for this component.
At the bottom of the page is a table listing all the properties of the
component with descriptions, type, and indicating whether the property is
required or not.
Settings
Renderer Application
The settings page allows you to change the renderer application being used.
At this time, there are three preloaded renderer applications:
- Angular Basic Catalog
- Lit Basic Catalog
- React Basic Catalog
If you are building another renderer app, you can select "Custom" from the
dropdown, and put the URL in the text box. (For instructions on building a
renderer app, see A2UI Composer Integration).
Gemini API Key
This is also where you can enter a Gemini API key, enabling use of the Gemini
chat feature.
To obtain an API key:
- Go to Google AI Studio and sign in
with your Google account. - Click Create API key.
- Select or create a Google Cloud project when prompted, then click Create key.
- Save your key in a secure location!
Note that the A2UI Composer encrypts your key and stores it locally in your
browser's secure database using the Web Crypto API.
Neither Google nor anyone else has access to this key.
Work in Progress
The A2UI team is actively working on the following improvements:
- Latency reduction: Improving latency within Gemini-assisted workflows.
- Visual understanding: Updating Gemini-assisted workflows to improve
visual understanding of already rendered surfaces.
Raw Messages
The messages which flow between the Composer and the renderer can help with
troubleshooting and to gain a better understanding of what's going on. These
messages include:
- RENDERER_READY: Sent by the renderer once it has fully bootstrapped.
- A2UI_CATALOG: Sent by the renderer (upon request from the Composer).
Contains the full A2UI catalog supported by the renderer. - COMPONENT_USAGES: Sent by the renderer (upon request from the Composer),
containing the data to support the Component Gallery page.
Whenever there is a change to the A2UI component's data model, the
DATA_MODEL_CHANGE message is captured, showing the updateDataModel
message which would be sent back to the agent.
Additionally, when using the Gemini chat feature:
- LLM_REQUEST: Shows the full request (including the system prompt) sent
to the LLM. - LLM_RESPONSE: Shows the full response received from the LLM.
Public Concepts Overview (`docs/public/concepts/overview.md`)
Core Concepts
This section explains the fundamental architecture of A2UI. Understanding these concepts will help you build effective agent-driven interfaces.
See Glossary for short definitions of key terms.
The Big Picture
A2UI is built around three core ideas:
- Streaming Messages: UI updates flow as a sequence of JSON messages from agent to client
- Declarative Components: UIs are described as data, not programmed as code
- Data Binding: UI structure is separate from application state, enabling reactive updates
Key Topics
[Data Flow](data-flow.md)
How messages travel from agents to rendered UI. Includes a complete lifecycle example of a restaurant booking flow, transport options (SSE, WebSockets, A2A), progressive rendering, and error handling.
[Component Structure](components.md)
A2UI's adjacency list model for representing component hierarchies. Learn why flat lists are better than nested trees, how to use static vs. dynamic children, and best practices for incremental updates.
[Data Binding](data-binding.md)
How components connect to application state using JSON Pointer paths. Covers reactive components, dynamic lists, input bindings, and the separation of structure from state that makes A2UI powerful.
Message Types
=== "v0.9 (Stable)"
Version 0.9 uses the following message types:
- **`createSurface`**: Create a new surface and specify its catalog
- **`updateComponents`**: Add or update UI components in a surface
- **`updateDataModel`**: Update application state
- **`deleteSurface`**: Remove a UI surface
v0.9 separates surface creation from rendering β `createSurface` replaces both `beginRendering` and the implicit surface creation in `surfaceUpdate`. All messages include a `version` field.=== "v1.0 (Candidate)"
Version 1.0 uses the following message types:
- **`createSurface`**: Create a new surface and specify its catalog
- **`updateComponents`**: Add or update UI components in a surface
- **`updateDataModel`**: Update application state
- **`deleteSurface`**: Remove a UI surface
- **`actionResponse`**: Respond to client-initiated actions
v1.0 introduces the `actionResponse` message type, enabling robust client-to-server synchronous RPC capabilities.=== "v0.8 (Legacy)"
Version 0.8 uses the following message types:
- **`surfaceUpdate`**: Define or update UI components
- **`dataModelUpdate`**: Update application state
- **`beginRendering`**: Signal the client to render
- **`deleteSurface`**: Remove a UI surfaceFor complete technical details, see Message Reference.
Contributing Release Pub Dev (`docs/contributing/release-pub-dev.md`)
Maintenance information for publishing to pub.dev
This page contains maintenance information for publishing to pub.dev.
It is needed when main steps stop working or something needs to be changed.
For main publishing steps, see release.md.
Versioning
We use Semver for package versioning, although before 1.0.0, we will be
incrementing only the minor number for breaking changes and the patch number for
non-breaking changes.
Depending on the package category, the process of publishing to pub.dev follows different rules:
The package is not published, and is not planned to be published
pubspec.yaml has publish_to: none.
The package is planned to be published as production-ready (but wasn't published as such before)
The package should be published to pub.dev with a -wip suffix to reserve the name and maybe to try the package for development purposes.
The package is published as production-ready
- The package's
version:inpubspec.yamldoesn't contain any suffix. - It is recommended to publish the changes after every pull requests.
- If your feature is partially implemented, hide the feature's code behind a false-by-default flag, and make sure the version at
mainis still ready for production.
CI
The following CI workflows support publishing, making sure the packages are always publish-ready:
In this repo:
In genui repo:
How to enable the workflows
For these workflows to function, the GitHub org needs to be configured.
This section instructs how to do that, in case some future reorg will require re-enabling them
(The permissions cover more than needed, because we may want to do more automation in future).
In https://github.com/organizations/a2ui-project/settings/actions:
Find the section "General actions permissions"
Either select "Allow all actions and reusable workflows" or choose "Allow enterprise, and select non-enterprise, actions and reusable workflows" and add these values (if they are already here, they will be de-dupped automatically):
textpeter-evans/create-or-update-comment@*, peter-evans/create-pull-request@*, peter-evans/repository-dispatch@*, dart-lang/ecosystem/.github/workflows/health.yaml@*, dart-lang/ecosystem/.github/workflows/post_summaries.yaml@*, dart-lang/ecosystem/.github/workflows/publish.yaml@*,
Contributing Release (`docs/contributing/release.md`)
Release process
How to publish packages
Release cadence: every 1-2 weeks.
Pub.dev publishing steps
Between releases, each Dart package in this repository collects the notes of
merged pull requests under ## Unreleased at the top of its CHANGELOG.md.version: in its pubspec.yaml stays at the last released version, and only a
release pull request changes it.
1. Find the packages to release
A package has something to release when its CHANGELOG.md has notes under## Unreleased:
2. Open a release pull request
In one pull request, for each package to release:
- Choose the next version, as described in
Versioning. - Rename
## UnreleasedinCHANGELOG.mdto## <version>. - Set
version:inpubspec.yamlto the same version.
Change nothing else, and merge the pull request.
3. Publish the packages
For each package in the release pull request:
- Check out the commit that merged the release pull request, not the latest
main, which may already have unreleased changes. - Run
flutter pub publishin the package directory, making sure the console
shows no warnings. - Verify that the correct version was uploaded to pub.dev.
If any step fails, file a GitHub issue and inform the team.
For troubleshooting and maintenance, see release-pub-dev.md.
Packages in the genui repository
In these changelogs, find the packages whose top version is not -wip... and
is not published yet (follow the link in the CHANGELOG.md header to verify),
then publish them from the latest main of that repository:
- genui CHANGELOG.md
- genui_a2a CHANGELOG.md
- genai_primitives CHANGELOG.md
- json_schema_builder CHANGELOG.md
NPM
See web_publishing.md.
PyPI
The Python packages are a2ui-core, a2ui-agent-sdk, and the agent framework integrations a2ui-a2a and a2ui-adk. Releasing one or more of them is performed by providing a prompt to an AI assistant within the a2ui project directory.
Release <package names, or all> with a <major|minor|patch> bumpName the packages to release, for example a2ui-agent-sdk or a2ui-a2a and a2ui-adk, or ask for all to release all four.
The assistant uses the a2ui-release-python skill to inspect changelogs, run local preflight checks, execute a dry run, confirm with you, trigger publishing via the GitHub Actions workflow, and open the changelog pull request. Once publication finishes, you only need to find a reviewer to approve and merge the pull request to update the changelog. Released packages appear on PyPI (a2ui-core, a2ui-agent-sdk, a2ui-a2a and a2ui-adk) as well as under corresponding Git tags and GitHub releases that link to them.
How it works behind the scenes
Publishing is handled by the Release Python SDKs workflow on main. There is no version file to edit and no package build or upload to run locally.
The workflow works out the new version from the latest release tag, tags the
release, builds, stages the artifacts in the OSS Exit Gate Artifact Registry,
and uploads the manifest that triggers publishing. The Exit Gate emails[email protected] when publishing starts and again when it
finishes. The GitHub release is updated with a link to the published version
once it appears on PyPI, either by the release run itself or by the hourly
Confirm PyPI publication
workflow.
If an assistant is unavailable, maintainers can dispatch the workflow manually from the Actions tab on main. Always run with dry_run: true first to verify the plan and artifact staging before running with dry_run: false.
The release only pushes tags
main is covered by a ruleset that requires a pull request and allows no bypass
actors, so the workflow cannot push to it. Being refused mid-run would leave
artifacts staged in the Artifact Registry, so the workflow does not try: it
pushes only tags, which no ruleset covers, and raises the changelog edit as a
pull request afterwards.
Tags therefore point at the commit that was the tip of main when the run
started, not at the changelog commit. That is deliberate. The repository
requires linear history, so a tag created on a branch commit would be left
unreachable once the pull request is squashed, and the git describe check in
python_ci.yml would start failing.
Why the workflow does not open the changelog pull request directly
The release workflow prepares the changelog edit on a release/changelog-* branch and
stops there. It could open the pull request, but that pull request could never
be merged: GitHub does not start workflow runs for events caused by the
built-in GITHUB_TOKEN, so none of the required checks would ever report, and
the ruleset allows no bypass. Opening it yourself from the link in the job
summary (or having an AI assistant open it using user credentials) allows
the required CI checks to run.
Versions come from git tags
Each package has its own tag series (python/a2ui-core/v*,python/a2ui-agent-sdk/v*, python/a2ui-a2a/v* and python/a2ui-adk/v*), and
hatch-vcs derives the package version from it at build time. Do not hand-edit a version anywhere; the version.py in each
package only reads the version back out of the installed distribution metadata.
A checkout without tags falls back to the version pinned as fallback-version
in pyproject.toml, which keeps shallow CI clones working. The release workflow
reads the version back out of every built artifact and fails if it does not
match the version it planned, so a release cannot go out on a fallback version.
Things that will stop a release
The preflight checks in
release_version.py fail the run when
the Unreleased section is empty, when the target version already has a tag,
when a proposed version falls outside the range that another package pins it
to, and when a package depends on another package of this repository that no
released version satisfies.
The pins form a chain: a2ui-agent-sdk pins a2ui-core, a2ui-a2a anda2ui-adk pin a2ui-agent-sdk, and a2ui-adk pins a2ui-a2a in its a2a
extra. A minor a2ui-core bump, for example, needs the pin in
a2ui_agent/pyproject.toml
widened in a pull request that is merged to main before the release is
dispatched. The workflow releases main as it is and cannot change the pin
during the run.
A dependency can be released in the same run: a release of several packages
publishes them in dependency order, a2ui-core, a2ui-agent-sdk,a2ui-a2a, a2ui-adk, so each package's new dependencies are on PyPI first.
Releasing a2ui-a2a or a2ui-adk on its own requires a released version of
each dependency that satisfies its pins.
a2ui-a2a and a2ui-adk start from 0.0.0, so their first release uses aminor bump to publish 0.1.0. Both require an a2ui-agent-sdk release newer
than 0.8.0, the last one that shipped a2ui.a2a and a2ui.adk itself. For
their first release, release a2ui-agent-sdk, a2ui-a2a and a2ui-adk
together with a minor bump, or release a2ui-agent-sdk first, merge its
changelog pull request, then release a2ui-a2a and a2ui-adk with a minor
bump.
If authentication fails
The Exit Gate authorizes this repository through Workload Identity Federation,
matching the workflow file path and branch exactly against an allowlist entry in
Google's internal project config:
builders: "github_workflow:a2ui-project/a2ui/.github/workflows/release-pypi.yml@refs/heads/main"Two consequences worth knowing. Renaming release-pypi.yml breaks releases
until the internal config is updated. And the workflow cannot be triggered by
pushing a tag, because the matched claim includes the triggering ref, which is
why the release is dispatched from main and creates its own tag rather than
being started by one.
To test access and artifact staging without publishing anything, run the
Release Python SDKs workflow withdry_run: true. See go/oss-exit-gate-builders and go/oss-exit-gate-onduty.
Documentation website
MkDocs, configured in .github/workflows/docs.yml, updates https://a2ui.org/ whenever the content of docs/public changes.
Internal troubleshooting and notes
See go/a2ui-release for internal information.
Contributing Triage Templates (`docs/contributing/triage-templates.md`)
A2UI Standard Triage Replies
Canonical replies for recurring triage cases.
Issues
Weekly A2UI Compliance Report
Case: AI-generated compliance report issues, such as
Weekly A2UI Compliance Report.
Triage: priority P3, action backlog (keeps the issue open).
As agreed, this has been assigned a P3 priority: we need better workflow to make it easier to follow up on the findings.
Assigned issue without a priority
Case: The issue already has an assignee but no priority label, so it sits in the triage
queue with nothing else to decide.
Triage: priority P2, action assign_and_fix, keeping the existing assignee.
I assigned a P2 priority to move this out of the triage queue. Please adjust the priority
if needed to better reflect the issue's status.
Stale P2 issue
Case: The issue is P2 but stale.
Triage: add comment.
This issue should be considered for a P1 priority together with other P2 issues in the next planning cycle after we burn through the triage queue.
Stale assigned P1 issue
Case: The issue is P1 and has an assignee, but has been stale for a while.
Triage: add comment.
Ping from triage process: is this issue still on your radar?
Out of Scope / Roadmap Conflict
Case: The user suggests a feature or change that is outside the current scope of the A2UI protocol roadmap.
Triage: priority P4, action backlog (keeps the issue open).
Thanks for the suggestion. This falls outside the current scope of the A2UI protocol roadmap.
Marked as P4: we do not plan to invest in it, and PRs against it will not be reviewed. Leaving it open as a record of the request β comment here if you have new arguments to share.
Pull requests
These cases are handled by the gardener directly. The dashboard and apply script
cover issues only, so the actions below are performed by hand.
Superseded PR
Case: A valid contribution that has gone stale and was replaced by a newer PR carrying
the original commit.
Triage: close the PR, linking the superseding one.
Thank you for your contribution! We apologize that this PR has become outdated. To
expedite the process, I created a superseding PR based on your analysis and changes. Your
original commit is included to ensure your contribution is properly recorded.<link to new PR>
Complex PR without an associated issue
Case: A complex PR that does not address an issue. Straightforward and minor PRs are exempt.
Triage: close the PR.
Thank you for your contribution. Unfortunately, we must decline this PR at this time.
Our team resources are limited, and our policy is to prioritize the review of PRs that address an existing issue prioritized issues. Consider opening issue
and link your PR in the issue description as one of the options for resolution.
PR outside the project's scope
Case: The change targets functionality the maintenance team cannot support long-term.
Triage: close the PR.
Thank you for your contribution to the A2UI ecosystem! Unfortunately, we must decline this PR as the scope of this repository is restricted to features the maintenance team can support long-term. We encourage you to find a more suitable repository for this code or to consider launching your own repository to support this segment of the A2UI ecosystem.
Complex PR towards existing issue but not assigned to the author
Case: Complex PR that is not assigned to the author.
It might be a good contribution, but we do not have enough bandwidth to review it at this time.
Triage: close the PR.
Thank you for your contribution to the A2UI ecosystem! Unfortunately, we must decline this PR at this time: we review PRs against an issue only from the contributor the issue is assigned to, and this one is not assigned to you. Issues are assigned in priority order, so we cannot say yet when this one will be picked up.
We will link this PR to the issue, so whoever takes it on can see your approach. They may reopen this PR and build on it rather than start from scratch.
Meaningful stale PR with conflicts
Case: The PR is meaningful, but has conflicts with the main branch.
Triage: assign status: waiting-for-user-response.
Thank you for your contribution. I am sorry it took so long for our team to review your PR. Unfortunately the branch has conflicts with the main branch. If you believe the issue is not resolved yet, can you, please, resolve the conflicts? We will review it again after you resolve conflicts.
PR that closes already closed issue
Case: The PR closes an issue that has already been closed.
Triage: close the PR.
Thank you for your contribution. I am closing this PR as the linked issue has already been resolved.
Contributing Triage (`docs/contributing/triage.md`)
Triage for PRs and issues
See go/a2ui-triage-internal for internal information.
Goals
Continuously make sure that on a2ui:
- External PRs are addressed
- Issues are prioritized and addressed
- Number of branches is observable
Invariant we want to keep
This section describes goals at a high level. See concrete steps in the section Triage responsibilities.
Issues priority (aligned with other teams in Dash):
- P0: very urgent, and it should be assigned
- P1: actively being worked on, and it should be assigned
- P2: is expected to be converted to P1 within three months, as part of regular planning process
- P3: not planned, but may become a priority
- P4: we do not plan to invest in this issue
PRs: We will review PRs from external contributors if at least one of the following is true:
- the PR contributes to a P0-P3 issue (PRs for P4 issues will not be reviewed)
- the issue is assigned to a contributor by the A2UI team (as assignee or in the first line of issue description) or has label
type: contributions-welcome - the change is, in the opinion of the A2UI team, absolutely clear and obviously needed
The relevant issue should be linked in the the PR description.
- Branches: list of stale branches should fit on one screen and should not have a button βNextβ.
GitHub labels used in triage
- Priority labels: P0, P1, P2, P3, P4
- status: needs-team-input
- status: needs-triage
- status: first-line-handled
- size: small
- type: contributions-welcome
- status: waiting-for-author-response
See all github labels.
Two of these labels are automated: status: needs-triage is managed entirely by the bot, and status: waiting-for-author-response is applied manually and automatically cleared once the author replies.
How the automated triage bot works
The triage.mjs script runs via the Flag/Unflag issues and PRs GitHub Actions workflow. It reconciles status: needs-triage across all open items according to the following rules:
- Skipped items: The automation skips the following items:
- Items waiting on author: If an item has
status: waiting-for-author-response, it is skipped. The automation removes this label once an external contributor posts a comment, review or review comment after the label was added. - Assigned issues: When an issue is assigned, it is assumed that a team member is looking into it.
- Items waiting on author: If an item has
- Issues: Flagged with
status: needs-triageif any of the following apply:- No priority: The issue lacks a priority label (
P0βP4). - Unassigned high priority: Labeled
P0orP1without an assignee. - Stale: Inactive beyond the priority threshold:
P0: stale for > 1 dayP1: stale for > 30 daysP2: stale for > 90 days- (
P3andP4issues are never flagged for staleness)
- Unanswered external comment: The latest human comment is from an external contributor.
- No priority: The issue lacks a priority label (
- Pull Requests: Flagged with
status: needs-triageif opened by an external contributor and no maintainer has responded to the author's latest contribution. (Maintainer-authored PRs are not flagged). - Transparency: The automation prints to the console what items are flagged or unflagged and why. This can be used to track triage progress.
Staleness is calculated from the last human contribution (comment, review, or issue/PR creation) rather than updated_at to avoid bot edits resetting the timer.
The workflow runs daily on a schedule (15:00 UTC / 07:00 PST), on manual dispatch, and automatically on issue events (opened, edited, labeled, unlabeled, assigned, unassigned, reopened), new issue comments, and submitted PR reviews or review comments.
Triage responsibilities
First line triage (daily)
For each issue that is not first-line-handled:
- If it is P0, add label
P0and notify team chat - Add label
status: first-line-handled
Second line triage (weekly)
Update items with label status: needs-triage, so that the label, managed by the triage bot, disappears. Do at least one of the following:
- Set priority (P0-P4)
- Be sure to assign P0/P1 issues to someone.
- Answer external comments
- Add
status: waiting-for-author-responsewhen you need more information from the author
You can remove the needs-triage label manually to speed up the process, but if the item still matches a rule, the label will come back because the label will be re-applied by the triage bot.
For items where you need input from the team, follow this process (passing any remaining follow-up to the next gardener if still in progress):
- add the
status: needs-team-inputlabel - post a message to the team chat, suggesting options and/or asking for input
- drive the discussion to resolution
- remove the label
status: needs-team-input
Use standard replies that are provided for standard cases.
Lastly, check for issues which are still unlabeled/unprioritized (without P0-P4 label and haven't had status: needs-triage, status: needs-team-input or status: waiting-for-author-response added to them). If you find such issues, follow the status: needs-triage workflow above (This is just to check for issues which somehow fall through the cracks).
When weekly triage is complete, the needs-triage list should ideally be empty.
AI assistance
Use .agents/skills/a2ui-issue-triage to get agent help with triage.
It forks multiple subagents (one for each issue) to try and reproduce the issue if it is something that can be easily reproduced. It won't attempt to repro something that takes a lot of setup.
It will then display a web UI in a local browser with the list of issues to triage, suggested responses (which you can edit) and a UI to apply suggestions to GitHub.
Frequently asked questions
Why do we allow branches on the repo and thus create work for maintainers?
Eval and e2e tests cannot be executed on pre-submit for PRs from forks, because they require an API key that is visible only on the original repo.
We want evals and e2e's to run on pre-submit at least for team members.
Watching branches is not big extra toil for triage process, because:
- If we forbid branches, the work will not disappear, it will just move to the fork
- It is easy to cleanup branches because ownership is clear
- As team members know it is part of triage to clean them up, they are more careful managing their branches
Why do we need P4? Why not just close the issue?
We need the label P4, because:
- Just closing the issue will not give a clear searchable sign why it is closed
- External developers can reopen issue, but cannot change label
It is better not to close P4, because:
As we have label P4 anyway, closing the issue is an extra step.
If P4 is open, it is a sign that this feature request is still:
- not implemented
- seems to be valuable
- considered P4
which adds clarity and makes it harder to push for prioritization.
Contributing Web Publishing (`docs/contributing/web_publishing.md`)
Publishing Guide for A2UI Web Packages
This guide outlines the workflow for project maintainers publishing web packages
(@a2ui/*) to npm through Google's internal Artifact Registry.
Prerequisites: Authentication
Publishing requires access to Google's internal Artifact Registry. Google Cloud
CLI must be authenticated before starting:
gcloud auth loginThe release scripts retrieve this authentication token usinggcloud auth print-access-token when needed.
Release Workflow
All scripts should be executed from the repository root. The workflow consists
of a pull request bumping the versions of the packages to release, followed by
publishing from the main branch.
1. Increment Versions (in a Pull Request)
increment_version.mjs can be run on a local branch to bump package versions
and synchronize locks:
# Increment patch version automatically (e.g., 0.9.5 -> 0.9.6)
./typescript/scripts/increment_version.mjs web_core
# Set an explicit version
./typescript/scripts/increment_version.mjs lit 0.10.0-beta.1This branch should be merged into main through a PR as with any other change
to the repo.
CLI parameters for increment_version.mjs:
- ``: The name of the package to update (e.g.,
web_coreor@a2ui/web_core). [new-version]: The specific new version to set (e.g.,1.0.1). If omitted, increments the patch version automatically.--skip-sync: Skip synchronizing dependent packages (runningyarn installin dependents). Not recommended.
2. Publish to Staging (from `main`)
Once the new versions land in main and it is checked out locally,publish_npm.mjs can be used to build, test, and upload the packages to
Google's internal Artifact Registry:
./typescript/scripts/publish_npm.mjs --package=lit --package=web_coreThis script publishes requested packages in the correct dependency order (e.g.,
ensuring web_core is published before lit), runs unit tests, and verifies
that required core packages exist on the registry.
By default, the script runs in dry-run mode to prevent accidental uploads; the--no-dry-run flag can be passed to actually upload the packages:
./typescript/scripts/publish_npm.mjs --package=lit --package=web_core --no-dry-runArtifacts are uploaded to: go/a2ui-oss-exit-gate-artifacts.
This URL points to a web app that allows verifying that the packages have been uploaded correctly.
For each package it should be checked:
- That it has been uploaded at the expected version
- That its size is non-zero in the "Files" tab of the details of the version.
CLI parameters for publish_npm.mjs:
-p, --package=<name>: Package(s) to publish. Can be specified multiple times. Accepts short names (e.g.,web_core) or scoped names (e.g.,@a2ui/web_core).--no-dry-run: Actually publish the packages. By default, the script runs in dry-run mode.--skip-tests: Skip building and testing packages before publishing. Not recommended.
3. Trigger Public Release (from `main`)
Once packages are verified in staging, a release manifest must be uploaded to
trigger the internal Exit Gate pipeline, which publishes them to npm:
./typescript/scripts/upload_manifest.mjs --package=web_core --package=litSimilarly to publish_npm.mjs, this script runs in dry-run by default, so the--no-dry-run flag must be passed to actually trigger the release:
./typescript/scripts/upload_manifest.mjs --package=web_core --package=lit --no-dry-runConfirmation emails from Exit Gate and npm reporting on the progress of the
actual publishing will be sent automatically. Publishing normally takes around
5 minutes.
CLI parameters for upload_manifest.mjs:
-p, --package=<name>: Package(s) to trigger release for (e.g.,--package=lit). Can be specified multiple times.--no-dry-run: Actually trigger the public release via Exit Gate. By default, the script runs in dry-run mode.
What is the `publish:package` yarn script doing?
A2UI web packages depend on each other via workspace:* links during development. When publish_npm.mjs invokes a package's publish:package target, the following preparation steps occur:
- Build & Metadata Transformation:
prepare-publish.mjscopies build output intodist/, replaces internalworkspace:protocols with absolute semantic version ranges (e.g.,^0.10.3), and strips development scripts/dependencies. - Boundary Isolation: Because the root workspace config excludes
dist/(!**/dist), an emptyyarn.lockis initialized insidedist/to establish it as an independent package boundary. - Clean Upload:
yarn npm publish --access publicexecutes strictly insidedist/, ensuring only clean production assets are uploaded.
What are valid values for the `--package` argument?
The increment_version.mjs, publish_npm.mjs and upload_manifest.mjs scripts
work with all publishable web packages in the typescript directory of the monorepo:
web_coremarkdown-itangularlitreact
The scripts also support the full package names, e.g. @a2ui/web_core, but the@a2ui/ prefix is not required.
Troubleshooting
- Dirty Working Tree Warnings: If build artifacts or temporary files
persist,yarn clean:allcan be run from the monorepo root before trying again.
Proposals Eval Suite Expansion (`docs/proposals/eval_suite_expansion.md`)
Evaluation dataset expansion and agentic data points design
Status: Draft
Author: A2UI Team
Created: 2026-07-23
Background
The A2UI evaluation framework located in eval/ tests whether Large Language Models generate valid and contextually appropriate A2UI JSON payloads. The current framework runs Inspect AI tasks (eval/tasks.py) against prompt datasets stored in YAML format. Datasets are encrypted at rest using Transcrypt to prevent data contamination from web crawlers.
While the existing system tests basic generation well, real-world agentic applications present scenarios that the current setup cannot evaluate:
- Monolithic dataset files: All evaluation samples reside in monolithic YAML files. There is no mechanism to isolate data points into separate modular datasets (such as
core_v0_9_1,customer_a_data, orbanking_app), execute only a specific dataset, or view separate reporting tables per dataset. - Disconnected single-string prompts: The existing schema (eval/datasets/dataset_schema.json) uses a standalone
promptTextstring. Real agentic applications pass a structuredmessageslist containing prior user queries, assistant responses, and tool calls with execution outputs before the model generates UI. - Domain system prompts and custom catalogs: In production applications, models receive domain-specific system prompts (such as customer support rules, flight booking domain logic, or medical guidelines) that are separate from A2UI protocol instructions. Applications also define custom component catalogs with specialized UI elements. The current eval engine only injects A2UI protocol instructions without merging custom domain system prompts or supporting multi-turn chat history.
- Contributor onboarding: Contributors and external teams creating new data points need a clear, step-by-step workflow covering decryption, schema verification, local evaluation execution, and safe encrypted commits.
This proposal outlines the technical design for expanding the evaluation framework to support separate modular datasets, standard LLM chat completion message formats (messages), custom catalogs with merged domain system prompts, and a contributor guide. All existing data points are cleanly migrated into modular dataset files under eval/datasets/, eliminating legacy monolithic files and obsolete field aliases.
Architecture overview
The expanded evaluation pipeline adds dataset discovery, standard LLM messages array parsing, system prompt merging, and dataset-aware reporting to Inspect AI.
/* Detailed source-code truncated for AI context efficiency. */1. Separate datasets of data points
Directory organization
Dataset files will be organized under eval/datasets/ as separate modular YAML files. The legacy monolithic files (prompts_v0_9_1.yaml and prompts_v1_0.yaml) are migrated directly into named dataset files:
eval/datasets/
βββ .gitattributes # Transcrypt filter rules
βββ dataset_schema.json # Updated JSON schema for data points
βββ defaults.py # Default prompt templates and catalog paths
βββ core_v0_9_1.yaml # Migrated core dataset for v0.9.1 protocol
βββ core_v1_0.yaml # Migrated core dataset for v1.0 protocol
βββ customer_a_data.yaml # Customer A specific agentic scenarios
βββ flight_booking.yaml # Domain-specific multi-turn datasetEach YAML file in eval/datasets/ represents a distinct evaluation dataset named after its filename (e.g. customer_a_data.yaml defines the customer_a_data dataset, and core_v0_9_1.yaml defines the core_v0_9_1 dataset). Alternatively, a sample can specify an explicit dataset property in its metadata.
Dataset resolution and CLI execution
The CLI runner in eval/main.py and the CI runner in eval/bin/run_ci_evals.py will support filtering by dataset:
# Run all datasets (default)
uv run main.py
# Run only a specific dataset
uv run main.py --dataset customer_a_data
# Run multiple specific datasets
uv run main.py --datasets customer_a_data,core_v0_9_1
# Run a quick sanity check on a specific dataset
uv run main.py --dataset customer_a_data --sanityCLI argument specifications
--dataset <name>: Evaluates only samples belonging to the named dataset.--datasets <name1,name2>: Evaluates samples belonging to any of the listed datasets (comma-separated).- When no dataset flag is supplied, the framework defaults to evaluating all active datasets or the default core datasets.
Dataset-aware reporting and summary tables
The evaluation reporting tools in eval/bin/report_evals.py will group and calculate metrics per dataset:
Console output format
=== Evaluation Results Summary ===
--- Dataset: customer_a_data (Pass Rate: 100.00%) ---
customer_a_flight_select | Algorithmic: PASS | Judging: C | Inference Time: 3.20s
customer_a_seat_picker | Algorithmic: PASS | Judging: C | Inference Time: 2.80s
--- Dataset: core_v0_9_1 (Pass Rate: 90.00%) ---
contact_form | Algorithmic: PASS | Judging: C | Inference Time: 1.90s
data_binding_list | Algorithmic: PASS | Judging: P | Inference Time: 2.10s
[Judging Failure Reason (Grade P)]:
Minor cosmetic variation in column label.
==================================
Dataset Summary:
customer_a_data : 2/2 passed (100.00%)
core_v0_9_1 : 9/10 passed (90.00%)
Overall Pass Rate: 91.67% (Threshold: 90.00%)
Inference Time - Average: 2.50s | Median: 2.45s
==================================Markdown summary format (`eval_summary.md`)
The generated markdown summary file will include a top-level dataset summary table followed by detailed per-dataset breakdowns:
### Evaluation Summary: a2ui_v0_9_1_eval
- **Status**: PASS
- **Model**: `google/gemini-3.6-flash`
- **Overall Pass Percentage**: `91.67%` (Threshold: `90.00%`)
#### Dataset Performance Summary
| Dataset Name | Total Samples | Algorithmic Pass Rate | Judging Pass Rate | Dataset Pass Rate |
| :---------------- | :-----------: | :-------------------: | :---------------: | :---------------: |
| `customer_a_data` | 2 | 100.0% | 100.0% | 100.0% |
| `core_v0_9_1` | 10 | 100.0% | 90.0% | 90.0% |
#### Detailed Dataset Results: customer_a_data
| Sample / Task | Algorithmic | Judging | Inference Time | Status |
| :----------------------- | :---------: | :-----: | :------------: | :----: |
| customer_a_flight_select | PASS | C | 3.20s | PASS |
| customer_a_seat_picker | PASS | C | 2.80s | PASS |2. Standard LLM chat schema and field reference guide
To align the evaluation format with industry-standard LLM chat completion APIs (such as OpenAI Chat Completions, Anthropic Messages API, and Inspect AI's internal state.messages), conversational turns and user prompts are consolidated under the familiar messages array. Every data point explicitly declares its required catalog path.
Standalone field reference
| Field Name | Type | Required? | Clear Explanation & Purpose |
|---|---|---|---|
name |
string | Yes | Unique identifier for this evaluation sample (e.g. customer_a_login_form). Used in logs and result tables. |
description |
string | Yes | Human-readable explanation of what this test case verifies. |
catalog |
string | Yes | Path to the component catalog JSON file defining available UI widgets and validation functions. Supports the {version} placeholder (e.g. specification/{version}/catalogs/basic/catalog.json). Required on every data point so solvers and scorers explicitly know which component schema and rules apply. |
messages |
array of message objects | Yes | The conversation messages. An ordered list of chat turns preceding and including the final UI request. Each item specifies a role (user, assistant, system, or tool), content, and optional tool_calls or tool_call_id. |
target |
string | No | The primary grading criteria and expected UI outcome. Used by the LLM-as-a-judge (measured_model_graded_qa) to evaluate whether the generated output meets requirements. If omitted, defaults to the value of description. |
dataset |
string | No | The name of the dataset module this sample belongs to. If omitted, the loader infers it from the YAML filename. |
system_prompt |
string | No | Domain-specific system instructions representing the host application (e.g., application persona, customer service rules, or API constraints). The eval engine merges this domain prompt with the A2UI protocol instructions. |
protocol_role |
string | No | The A2UI persona instruction passed to the SDK prompt generator. Specifies the high-level role for UI generation (Default: "You are an AI assistant. Based on the following request, generate a stream of JSON messages that conform to the provided JSON Schemas."). Most contributors can leave this omitted. |
generation_rules |
string | No | Technical protocol constraints appended to the SDK workflow prompt. Defines formatting rules such as required surface ID naming, component root ID constraints, or adjacency list rules. Most contributors can leave this omitted to use standard protocol defaults. |
allowed_surface_ids |
array of strings | No | List of valid surface identifiers the model is allowed to target (Default: ["main"]). |
Detailed breakdown of key concepts
1. Why `catalog` is required
Every A2UI evaluation requires a component catalog. Making catalog mandatory removes ambiguity for contributors, solver prompt generators, and validators. A contributor always explicitly declares the catalog path (such as specification/{version}/catalogs/basic/catalog.json for standard protocol widgets or a domain catalog path like specification/{version}/catalogs/customer_a/catalog.json).
2. Why `messages` is the preferred terminology
Across the LLM ecosystem (OpenAI, Anthropic, LiteLLM, Ollama, and Inspect AI), messages is the universal term for chat interaction sequences:
- Direct framework alignment: Inspect AI stores conversation turns in
state.messages. Usingmessagesin our YAML schema creates a 1:1 conceptual mapping. - Avoids ambiguity: The word "context" is overloaded in AI (often referring to RAG retrieval chunks, context windows, or A2UI action event contexts like
action.event.context).messagesunambiguously refers to the chat history. - Consistent single-turn and multi-turn authoring:
- Single-turn prompt:
messagescontains a single user turn (role: user,content: "Create a login form"). - Multi-turn conversation:
messagescontains prior user, assistant, and tool turns, ending with the user turn that prompts the UI response.
- Single-turn prompt:
3. `target` vs `description`
descriptionis a high-level summary of what the test case does, used for human documentation and readability in test manifests.targetis the detailed rubric evaluated by the grading model. It specifies the exact expectations for the UI, such as required component types (e.g. "Must contain a Card with a TextField and a primary Button"), data bindings (e.g. "TextField must bind to/auth/email"), and interactive actions.
4. `system_prompt` vs `protocol_role`
system_promptrepresents the domain application persona. In a real application, an agent has domain rules unrelated to A2UI (for example: "You are the Acme Airlines Agent. Always confirm passenger baggage before checkout.").protocol_rolerepresents the A2UI protocol prompt. This is the standard SDK instruction telling the LLM that it must output structured JSON conforming to the A2UI schema.- The eval engine merges both: domain instructions are presented first, followed by protocol instructions.
5. `generation_rules`
- This field controls the technical A2UI rules injected into the prompt's
## Workflow Description:section. - Standard protocol rules (e.g. "Construct the UI on surfaceId 'main'" and "Ensure there is one root component with id 'root'") are injected by default.
- If a test case needs custom protocol behavior (for example, rendering to a surface named
'sidebar'), the contributor specifies those extra rules ingeneration_rules.
JSON schema specification
The schema in eval/datasets/dataset_schema.json validates the clean dataset structure with catalog required:
/* Detailed source-code truncated for AI context efficiency. */Example multi-turn agentic data point (YAML)
Below is an example of an agentic data point representing a flight booking assistant that interacts via tools before rendering a custom A2UI interface:
/* Detailed source-code truncated for AI context efficiency. */Example single-turn data point (YAML)
Below is an example of a migrated single-turn core data point:
- name: contact_form
dataset: core_v0_9_1
description: Renders a contact form with email and submit button.
catalog: 'specification/{version}/catalogs/basic/catalog.json'
messages:
- role: user
content: 'Create a contact form with an email text field and submit button.'
target: |
The response must contain a Card or Column with a TextField bound to '/contact/email' and a primary Button.3. Eval engine execution mechanics
When executing an evaluation sample, the solver in eval/a2ui_eval/strategies/format.py coordinates prompt merging, chat message list assembly, inference, and scoring.
Step 1: System prompt generation and merging
The solver merges the domain-specific system prompt with the protocol instructions generated by the A2UI SDK:
- SDK prompt generation: The A2UI SDK
DirectJsonPromptGeneratorloads the requiredcatalogand generates protocol rules, JSON schemas, component catalog definitions, and output tag requirements (<a2ui-json>). - Domain prompt merging: If the data point includes a
system_prompt, the solver combines them into a unified system instruction:
def merge_system_prompts(domain_prompt: str, a2ui_prompt: str) -> str:
"""Merges domain-specific rules with A2UI protocol instructions."""
parts = []
if domain_prompt.strip():
parts.append(f"## Domain Instructions\n{domain_prompt.strip()}")
if a2ui_prompt.strip():
parts.append(f"## UI Protocol Instructions\n{a2ui_prompt.strip()}")
return "\n\n".join(parts)- The merged prompt is inserted as the first message (
ChatMessageSystem) in Inspect AI'sstate.messages.
Step 2: Chat messages assembly
The solver populates state.messages directly from the sample's messages array:
- User turns: Converted to
ChatMessageUser(content=turn["content"]). - Assistant turns: Converted to
ChatMessageAssistant(content=turn.get("content", ""), tool_calls=turn.get("tool_calls")). - Tool turns: Converted to
ChatMessageTool(content=turn["content"], tool_call_id=turn["tool_call_id"]).
The final turn in messages triggers the assistant's UI generation response.
Step 3: Model inference
The solver invokes measured_generate(), passing the assembled message list to the model. The model generates its completion on the final turn.
Step 4: Multi-stage scoring
- Programmatic validation (a2ui_scorer):
- Resolves the required
catalogpath specified in the data point. - Parses JSON from
<a2ui-json>tags. - Runs the catalog validator (validate_components) to verify schema adherence, parent-child references, and root component presence.
- Resolves the required
- LLM judge scoring (measured_model_graded_qa):
- Evaluates the generated UI against the
targetcriteria using the grading model (e.g.google/gemini-3.6-flash). - Produces a grade of
C(Correct),P(Partial Credit), orI(Incorrect).
- Evaluates the generated UI against the
4. Contributor guide: checking in data points and encryption
This step-by-step guide is for contributors adding or updating data points in the A2UI evaluation framework.
Why data points are encrypted
Evaluation datasets are encrypted at rest in Git using Transcrypt. This prevents public search engines and automated LLM training web crawlers from indexing our test prompts and answers, avoiding artificial score inflation from model memorization.
Step 1: Initial decryption setup
Before modifying or viewing datasets, initialize Transcrypt with the repository evaluation password:
cd eval
bin/transcrypt -p <PASSWORD>(Request the password from an A2UI maintainer if you do not have it.)
Once decrypted, all YAML files in eval/datasets/ appear as regular plaintext files on your local filesystem.
Step 2: Create or update a dataset file
- Create a new YAML file under
eval/datasets/<dataset_name>.yaml(for example,eval/datasets/customer_a_data.yaml). - Add your evaluation samples following the standard chat completion format with a required
catalog:
- name: customer_a_login_form
dataset: customer_a_data
description: Tests generation of a secure login card with email and password fields.
catalog: 'specification/{version}/catalogs/basic/catalog.json'
system_prompt: 'You are the Customer A Authentication Agent. Help users sign in.'
messages:
- role: user
content: 'Create a login form with email, password, and a submit button.'
target: |
The response must contain:
1. A Card or Column root component.
2. A TextField for email bound to '/auth/email'.
3. A TextField for password bound to '/auth/password'.
4. A primary Button labeled 'Sign In'.Step 3: Validate your dataset against the schema
Run the automated dataset schema test before running evaluations:
uv run python -m pytest tests/test_dataset.pyYou can also validate specific dataset files directly:
uv run python -m a2ui_eval.dataset --validate datasets/customer_a_data.yamlIf your YAML file violates the schema (e.g. missing required fields like catalog or unknown properties), the validator will output the exact line and error.
Step 4: Run a local evaluation test
Test how your new data points perform against Gemini models:
# Set your API key
export GEMINI_API_KEY="your_api_key"
# Run a quick sanity check on your new dataset (2 samples, flash-lite)
uv run main.py --dataset customer_a_data --sanity
# Run the full evaluation on your dataset
uv run main.py --dataset customer_a_dataStep 5: Inspect results in the log viewer
Start Inspect AI's interactive web viewer to examine model outputs, merged system prompts, and validator rationales:
uv run inspect view startOpen http://localhost:7575 in your browser to view the trace of each sample.
Step 6: Commit and push changes
Because .gitattributes configures Transcrypt filters for *.yaml files in the eval/datasets/ directory, Git will automatically encrypt the files when staging:
git add eval/datasets/customer_a_data.yaml
git commit -m "feat(eval): add customer_a_data evaluation dataset"
git push origin your-branchTo verify that files are encrypted before pushing, run:
git diff --cachedIf Transcrypt is active, the staged diff will show encrypted cipher text.
5. Migration path for existing data points
The evaluation framework migrated legacy monolithic prompt files (prompts_v0_9_1.yaml and prompts_v1_0.yaml) into clean, modular dataset files (core_v0_9_1.yaml and core_v1_0.yaml) conforming to the new messages schema.
Migration strategy
- Standardizing on
messages: Single-stringpromptTextqueries are converted into structuredmessagesarrays with role-based conversation turns (user,assistant,tool,system). - Mandatory component catalogs: Every sample specifies an explicit relative
catalogpath (e.g.specification/{version}/catalogs/basic/catalog.json). - Modular dataset separation: Data points are partitioned into logical, manageable YAML files under
eval/datasets/that are dynamically discovered by the dataset loader. - In-memory runtime fallback (loader safety): To prevent breakage if a developer branch or scratch file still contains legacy fields, the dataset loader in eval/a2ui_eval/dataset.py includes a runtime normalizer that converts
promptTexttomessagesand maps legacy field aliases on the fly.
Code changes roadmap
| File Path | Nature of Change | Purpose |
|---|---|---|
| eval/datasets/dataset_schema.json | Update | Require catalog, use messages for chat turns, and include dataset, system_prompt, protocol_role, and generation_rules fields. |
| eval/datasets/core_v0_9_1.yaml | Create | Migrated v0.9.1 data points using the messages format and explicit catalog. |
| eval/datasets/core_v1_0.yaml | Create | Migrated v1.0 data points using the messages format and explicit catalog. |
| eval/a2ui_eval/dataset.py | Update | Add dataset discovery, messages array conversion to Inspect AI ChatMessage objects, required catalog enforcement, and validation CLI. |
| eval/a2ui_eval/strategies/format.py | Update | Implement domain system prompt merging and messages chat history prepending. |
| eval/a2ui_eval/scorers.py | Update | Use explicit catalog path in a2ui_scorer. |
| eval/tasks.py | Update | Point to migrated datasets and accept dataset and datasets filtering parameters. |
| eval/main.py | Update | Add --dataset and --datasets CLI flags. |
| eval/bin/report_evals.py | Update | Add per-dataset grouping in console summaries and markdown reports. |
| eval/bin/run_ci_evals.py | Update | Pass dataset filtering arguments to main.py. |
| eval/tests/test_dataset.py | Update | Add unit tests for dataset loading, messages chat turn parsing, required catalog check, and schema validation. |
Proposals Spec Driven Development (`docs/proposals/spec_driven_development.md`)
**Scaling A2UI codebases with spec-driven development**
**Background**
The A2UI team has adopted recent development practices of using agents to write the majority of our code, using designs, specifications, prompts and other codebases as inputs to guide it. In this paradigm, the job of human developers is primarily to write and maintain the documentation that agents use to write code. This spec-driven development (SDD) approach is being adopted across the software industry in various ways. Members of the A2UI team have tried many different approaches, including generating detailed design docs which are then reviewed by humans for implementation, maintaining markdown guides which can be used to implement multiple variants of a codebase (e.g. renderer_guide.md for web renderers) or creating detailed Github issues that are used by agents to implement features.
The A2UI team aims to write and maintain several codebases across multiple languages with a small team. We have an opportunity to do this more efficiently by formalizing our use of spec-driven development, and building shared tooling to help agents follow it smoothly. We can reuse existing approaches to spec-driven development, but adapt them to work with our specific circumstances where we own a family of codebases which implement the same functionality across different languages.
**Goals**
Adopt spec-driven development with a standardized approach across the A2UI team in service of the following goals:
- Encourage a culture of where we spend more effort creating and reviewing specifications.
- Allow the A2UI team to more quickly implement features across SDK implementations in multiple languages.
- Allow contributors outside of the core A2UI team to more efficiently write and maintain their own A2UI SDKs, for example the Jetpack Composer and Swift UI renderers.
- Allow the A2UI team to collaborate more efficiently by standardizing the way that we record SDK and feature specifications.
- Increase the productivity of agents working in our codebase on any task by giving them better access to relevant design documentation.
- Make it easier for humans and agents to understand the discrepancies between different SDK codebases by emphasizing better definition of features and a clear separation between required features and optional (a.k.a. new or experimental) features.
- Avoid disrupting existing workflows used by team members and ensure they still have freedom to experiment with novel techniques.
**Overview**
This document outlines the Spec-Driven Development (SDD) methodology for the A2UI repository. The goal of SDD is to streamline the implementation of the A2UI protocol and its features across multiple programming languages and UI frameworks by establishing clear, language-agnostic specifications (called Blueprints) and leveraging AI coding agents to scale development.
graph TD
FB[Feature Blueprint] -->|Merges into| MB[Module Blueprint]
FB -->|Guides implementation of| CB[Codebase Blueprint]
MB -->|Specifies architecture of| CB
CB -->|Documents| C[Codebase Implementation]Under this model:
- Module Blueprints define the high-level architecture, core interfaces, and behavior of repository modules (e.g.,
a2ui_core,a2ui_inference,a2ui_react, etc.). - Feature Blueprints describe specific feature additions or behavioral changes to a module.
- Codebase Blueprints reside within each platform's implementation folder, tracking implemented features and documenting local design decisions or deviations.
Formal blueprints are primarily used for major features, e.g. the addition of a new public API, behavior, protocol version or architectural change. By having these specifications to be checked into the codebase, we create a natural opportunity for review of feature designs before they are implemented. We also ensure that when we add a feature to one SDK codebase, we can easily port it to other languages.
**Smaller features**
Smaller features can still be implemented ad-hoc with no formal blueprint. These small feature typically:
- Make changes to functionality that is not explicitly documented in the module blueprint, or
- Address local bugs, refactorings, or performance optimizations that do not affect the public API or cross-language protocol compliance, or
- Add codebase-specific utility functions or internal helpers that do not impact compatibility or consistency with other SDKs.
Specification-driven development is still useful for these features, but the specifications donβt always need to be checked into the file system. They could be checked in as blueprints, or written in Github issues, or exist as temporary files on a developerβs file system.
**Document types**
**Feature blueprint**
Feature blueprints describe a diff in functionality that can be implemented in a codebase.
**Required features**
A Required Feature Blueprint describes a feature that has its design merged into all relevant module blueprints. It is expected to be eventually implemented in all codebases for the module. Required feature blueprints are added (or promoted from optional status) before they are implemented in all codebases and there are no guaruntee that the feature is implemented in all codebases within any particular timeframe. It is the job of codebase owners to keep their feature sets as up to date as possible. The codebase blueprints are the source of truth for which features are implemented in each codebase.
- Agents implementing new codebases for a particular module never need to read required feature blueprints. They can completely implement the module and all its required features using only the module blueprint, because it has all the necessary details from existing required feature blueprints.
- Agents implementing new required features in existing codebases primarily use the required feature blueprint to design and implement changes.
**Optional features**
An Optional Feature Blueprint describes a feature that is not baked into the module blueprint and is not expected to be implemented in all codebases. It allows platforms to support experimental or framework-specific features without forcing compliance across all SDKs.
- Decoupled Lifecycle: Optional feature blueprints exist as standalone specification files in
blueprints/features/and are not merged into the base Module Blueprint. - Ad-hoc Implementation: Each codebase can decide independently whether to support an optional feature based on platform capabilities and user needs.
- Discovery: Codebases that implement an optional feature must list it in their
codebase.blueprint.mdunderimplemented_featuresto let clients and agents know it is supported.
**Feature dependencies**
A feature blueprint can optionally specify a list of dependencies in its frontmatter. This list includes other feature blueprints that this feature directly depends on. The purpose of this field is to provide a pragmatic way for coding agents to identify other recent features that might be missing from a codebase and need to be implemented first, rather than being an exhaustive list of all architectural dependencies.
**my_feature.blueprint.md structure (Example)**
Every feature blueprint must follow a standardized Markdown structure with YAML frontmatter. The feature is named with a date prefix, e.g. YYYY_MM_DD_feature_name.blueprint.md, so that if there are many feature blueprints, it's easy to sort them by date and find the most recent and relevant ones. The date in the filename and frontmatter must match, and it is the date that the feature blueprint was initially written.
(Note: This date is used solely for rough chronological sorting of features. It does not indicate any dependency structureβa feature does not necessarily depend on previously created features, nor does it imply it does not depend on features proposed later.)
Below is an example feature blueprint for a dynamic theming feature, written in a language-agnostic way:
/* Detailed source-code truncated for AI context efficiency. */**Module blueprint**
A Module Blueprint describes an entire architectural module in a language-agnostic way. It serves as the primary source of truth for building a new codebase from scratch or verifying the correctness of an existing one.
All required features that affect the module are merged into the module blueprint when the feature is added or promoted, so that new codebases can be implemented using only the module blueprint and without consulting specific feature blueprints. Module blueprints act as a coherent, compacted description of the module, ensuring that the overall module specification does not have unbounded growth over time as features are added. As features are layered on top of each other, it is expected that the module blueprint will find concise ways to describe the overall system which are smaller than a simple concatenation of the feature blueprints.
**a2ui_core.blueprint.md Structure (Example)**
Every module blueprint must follow a standardized Markdown structure with YAML frontmatter. Below is an example module blueprint for the core state layer (a2ui_core):
---
name: a2ui_core
included_features:
- bidirectional_rpc
- dynamic_binding
---
# **Core State Layer (a2ui_core) Module Blueprint**
## **Architecture Overview**
The core state layer is the framework-agnostic engine of A2UI. It is responsible for parsing inbound JSON streams from the agent, maintaining the active UI surface models, resolving JSON Pointer data bindings, and dispatching client actions back to the transport layer.
## **Core Interfaces**
- **`MessageProcessor`**: The central controller that ingests `A2uiMessage` envelopes and directs updates.
...
## **Conformance Test Plan**
Every implementation of `a2ui_core` must pass the core conformance test suite:
- **JSON Parsing Suite**: Validates envelope compliance against `server_to_client.json` and throws `A2uiValidationError` on schema violations.
- **Data Model Binding Suite**: Verifies absolute and relative pointer resolution, type coercion rules, and reactive signal emission.
- **Path to Mock Data**: `specification/v1_0/test/conformance/a2ui_core/`**Codebase blueprint**
A Codebase is a concrete, language-specific or framework-specific implementation of a module. Examples of codebases in this repository include typescript/core/web_core (TypeScript implementation of a2ui_core), typescript/renderers/react (React implementation of a2ui_react), and kotlin/agent_sdk_legacy (Kotlin implementation of a2ui_inference and a2ui_core).
Every codebase must contain a codebase.blueprint.md file in its root directory. This file maps the concrete implementation back to the language-agnostic module blueprint, tracking its feature support and documenting local engineering decisions. The codebase blueprint should be updated at the same time as the code to keep it consistent with the codebase.
**codebase.blueprint.md Structure (Example)**
Below is an example codebase blueprint for a React renderer codebase (a2ui_react):
---
associated_module: a2ui_react
implemented_features:
- dynamic_theming
- call_function_rpc
---
# **React Renderer Codebase Blueprint**
## **Architecture & Styling**
This codebase implements the `a2ui_react` module blueprint using React 19 and standard functional components.
* **Reactivity**: We map the core `a2ui_core` state signals to React state via a custom `useSignal` hook to trigger declarative re-renders.
* **Context Propagation**: We use React Context (`ComponentContext.Provider`) to propagate the data binding scopes recursively down the widget tree.
## **Technical Decisions & Overrides**
* **Dynamic Binders**: Unlike static languages, we utilize TypeScript runtime reflection to implement **Generic Binders**. This automatically resolves `DynamicString` properties into static props at the adapter layer, avoiding manual boilerplate for custom components.
* **Event System**: To handle React's synthetic event loop, we throttle input change actions (e.g., `TextField` keystrokes) in the adapter before committing them to the local `DataModel` to optimize performance.**Developer journeys**
This section explains what steps will be taken by developers and agents to perform common tasks using spec-driven development.
**Specify a new required feature**
- Create required feature blueprint (significant human input required)
- Update module blueprint based on the feature blueprint, to ensure the module blueprint fully specifies the feature and how to implement it. Add the feature name to the moduleβs βincluded featuresβ. (coding agent)
- Send PR for review. You can also include an implementation in a codebase in the same PR if you want - see βImplement an optional or required feature in a codebaseβ.
**Specify a new optional feature**
- Create optional feature blueprint (significant human input required)
- Send PR for review. You can also include an implementation in a codebase in the same PR if you want - see βImplement an optional or required feature in a codebaseβ.
**Promote an optional feature to be required**
- Update module blueprint based on the feature blueprint, and add it to the moduleβs βincluded_featuresβ. (coding agent)
- Send PR for review.
**Implement an optional or required feature in a codebase**
- Verify that the codebase does not already contain the feature
- Create a temporary design describing in detail how the feature should be implemented in the specific codebase, taking into account the feature blueprint, the codebase blueprint, and the actual codebase code. This file should not be checked in. (human input required)
- Use the temporary design to implement the feature
- Update the codebase blueprint to add the feature to the "implemented_features" list and include any codebase-specific decisions that were made as part of the feature implementation.
- Send PR for review.
**Implement all the features necessary to bring a codebase βup to dateβ**
- Read the codebase blueprint and module blueprint and identify all the required features in the module that are not in the codebase.
- Implement each feature in chronological order, based on their blueprints, following the steps to implement a feature above.
- Consult the module blueprint and verify that the codebase now matches it, making minor changes to the codebase as necessary to make it as consistent as possible to the module blueprint.
- Update the codebase blueprint to add the feature to the "implemented_features" list.
- Send PR for review, either one PR per feature, or one PR for the entire update.
**Resolve inconsistencies between a module blueprint and all of its associated codebases**
- Read the relevant module blueprint
- Search for all codebases that associated with the module
- Analyse every codebase associated with the module, identifying:
- Required features that are missing from each codebase
- Discrepancies between the blueprint and the actual implementation e.g. API names or structures which are inconsistent
- Discrepancies between the codebases, for which the module blueprint provides no guidance.
- Report the above and propose actions to take to reduce the inconsistencies including:
- Adding additional detail to the blueprints to reduce ambiguity
- Update the module blueprint to explicitly mark a detail as being a codebase-level decision
- Updating codebases to match the module blueprints
- Update the codebase blueprint to document a reason that it has intentionally deviated from the module blueprint for a language-specific reason.
- Implement some of the proposed actions, based on human discretion (significant human input required)
- Send PR for review.
**Implement a new codebase**
- Create a temporary design describing in detail how the codebase should be implemented based on the module blueprint. (significant human input required)
- Implement the module based on the temporary design
- Create a new codebase blueprint, summarizing the design of details that are not specified in the module blueprint.
- Split up the changes into manageable chunks to review over several PRs.
**Clean up feature blueprints**
Feature blueprints undergo a clean, Git-centric lifecycle to prevent the blueprints directory from becoming cluttered with obsolete specifications.
- Required Features: Once a required feature has been fully implemented in all active codebases and its requirements have been integrated into the base Module Blueprint, the feature blueprint file is moved to the archived/ folder.
- Optional Features: Optional feature blueprints remain in
blueprints/features/as long as they are actively supported. If they are promoted to required, they are integrated into the Module Blueprint and deleted. If they are deprecated or abandoned, they are moved to the archived/ folder.
**Implementation**
**Folder structure**
To keep specifications organized and isolated by default, all SDD blueprints, validation tools, and specialized skills reside in the top-level /blueprints/ directory:
/
βββ blueprints/
β βββ README.md # Directive instructing agents to ignore folder unless explicitly requested
β βββ validate_blueprints.py # Blueprint frontmatter and structure validator
β βββ link_skills.sh # Helper script to symlink blueprint skills into .agents/skills/
β βββ modules/ # Language-agnostic Module Blueprints
β β βββ a2ui_core.blueprint.md
β β βββ a2ui_framework_adapter.blueprint.md
β βββ features/ # Unmerged / Optional Feature Blueprints (no date prefix)
β β βββ dynamic_theming.blueprint.md
β β βββ archived/ # Merged / Required Feature Blueprints
β β βββ bidirectional_rpc.blueprint.md
β βββ codebases/ # Codebase Blueprints tracking module compliance by commit hash
β β βββ typescript/
β β β βββ core/
β β β β βββ web_core/
β β β β βββ codebase.blueprint.md
β β β βββ renderers/
β β β βββ react/
β β β βββ codebase.blueprint.md
β β βββ python/
β β βββ a2ui_core/
β β βββ codebase.blueprint.md
β βββ skills/ # SDD Blueprint Skills (isolated from default agent use)
β βββ a2ui-blueprint-maintenance/
β βββ a2ui-blueprint-navigator/
β βββ a2ui-create-feature-blueprint/
β βββ a2ui-implement-feature-from-blueprint/**Skills**
To support automated execution of spec-driven tasks without cluttering default agent contexts, specialized SDD skills are maintained in blueprints/skills/. Developers can optionally symlink them into .agents/skills/ using blueprints/link_skills.sh:
# Enable SDD skills locally
./blueprints/link_skills.sh
# Remove local SDD symlinks when done
rm -f .agents/skills/a2ui-blueprint-navigator \
.agents/skills/a2ui-create-feature-blueprint \
.agents/skills/a2ui-implement-feature-from-blueprint \
.agents/skills/a2ui-blueprint-maintenancea2ui-blueprint-navigator: A read-only analytical guide. It discovers blueprints and inspects codebase compliance against module blueprint git commit hashes (module_blueprint_commit).a2ui-create-feature-blueprint: Provides instructions on how to create a feature blueprint insideblueprints/features/without date prefixes.a2ui-implement-feature-from-blueprint: Provides instructions on implementing feature specs and updating codebase compliance commit hashes.a2ui-blueprint-maintenance: Manages merging feature specs into Module Blueprints and archiving feature blueprints toblueprints/features/archived/.
**Blueprint validation**
We implement a blueprint validator script (blueprints/validate_blueprints.py) that verifies all blueprints conform to required schemas and naming rules. It runs on CI (.github/workflows/validate_blueprints.yml) to block submission of invalid blueprints.
The validation script checks:
- Frontmatter compliance: Verify all mandatory YAML fields are present and correctly typed.
- Entity naming rules: Ensure feature names and module names use snake_case and match their filenames (
feature_name.blueprint.md). - Commit hash & reference integrity: Validate that
codebase_path,associated_module, optionalmodule_blueprint_commit, andimplemented_featuresin codebase blueprints point to valid targets.
**Creation of initial blueprints**
- Module blueprints: We will migrate our existing guides and documentation in the blueprint formats described above. E.g. renderer_guide.md will become the module blueprints for core and framework adapter layers.
- Codebase blueprints: We will have Gemini generate these by analysing the codebases.
Public Roadmap (`docs/public/roadmap.md`)
Roadmap
This roadmap outlines the current state and future plans for the A2UI project. The project is under active development, and priorities may shift based on community feedback and emerging use cases.
Current Status
Protocol
| Version | Status | Notes |
|---|---|---|
| v0.8 | π Prior | Initial public release, minimal support |
| v0.9 | π Prior | Feature complete, legacy support |
| v0.9.1 | π Current | stable release |
| v1.0 | π Candidate | release candidate |
Renderers
| Client libraries | Status | Platform | Notes |
|---|---|---|---|
| Web Core Lib | β Stable | Web | Shared core lib for all web renderers |
| Web Components (Lit) | β Stable | Web | Framework-agnostic, works anywhere |
| Angular | β Stable | Web | Full Angular integration |
| Flutter (GenUI SDK) | β Stable | Multi-platform | Works on mobile, web, desktop |
| React | β Stable | Web | Official React renderer |
| Lynx | β Stable | Mobile, Web, Desktop | ReactLynx renderer for A2UI v0.9 |
| SwiftUI | π Planned | iOS/macOS | Planned for Q2 2026 |
| Jetpack Compose | π Planned | Android | Planned for Q2 2026 |
| Vue | π‘ Proposed | Web | Community interest |
| Svelte/Kit | π‘ Proposed | Web | Community interest |
| ShadCN (React) | π‘ Proposed | Web | Community interest |
Transports
| Transport | Status | Notes |
|---|---|---|
| A2A Protocol | β Complete | Native A2A transport |
| AG-UI | β Complete | Day-zero compatibility |
| REST API | β Complete | Request/response over HTTP |
| WebSockets | β Complete | Bidirectional WS |
| MCP (Model Context Protocol) | β Complete | Context sharing |
Agent frameworks
| Integration | Status | Notes |
|---|---|---|
| Any agent with A2A support | β Complete | Day-zero compatibility thanks to A2A protocol |
| Any agent with AG-UI support | β Complete | Day-zero compatibility thanks to AG-UI protocol |
| AG2 | β Complete | A2UIAgent |
| ADK | π§ In Progress | Still designing developer ergonomics, see samples |
| Genkit | π‘ Proposed | Community interest |
| LangGraph | π‘ Proposed | Community interest |
| CrewAI | π‘ Proposed | Community interest |
| Claude Agent SDK | π‘ Proposed | Community interest |
| OpenAI Agent SDK | π‘ Proposed | Community interest |
| Microsoft Agent Framework | π‘ Proposed | Community interest |
| AWS Strands Agent SDK | π‘ Proposed | Community interest |
Recent Milestones
Q2 2025
Many research projects across multiple Google teams, including integration into internal products and agents.
Q4 2025 v0.8
- v0.8.0 spec released
- A2A extension (thanks Google A2A team! teased at a2asummit.ai)
- Flutter renderer (thanks Flutter team!)
- Angular renderer (thanks Angular team!)
- Web components (Lit) renderer (thanks Opal team & friends!)
- AG-UI / CopilotKit integration (thanks CopilotKit team!)
- Github public release (Apache 2.0)
Q2 2026 v0.9
- Release spec 0.9
- Release web core and renderers to support spec 0.9
- Launch official A2UI React renderer
- Improve theming support for renderers
- Launch A2UI Agents SDK (python)
- Improve developer ergonomics
Upcoming Milestones
Q3 2026 v0.9 & v1.0
Jetpack Compose Renderer (Android):
- Native Compose UI components
- Material Design 3 support
- Android platform integration
SwiftUI Renderer (iOS/macOS):
- Native SwiftUI components
- iOS design language support
- macOS compatibility
Performance Optimizations
- Renderer performance benchmarks
- Lazy loading for large component trees
- Virtual scrolling for lists
- Component memoization strategies
Q4 2026 v1.0
Finalize v1.0 of the protocol with:
- Stability guarantees
- Migration path from v0.9
- Comprehensive test suite
- Certification program for renderers
Long-Term Vision
Full App UIs
Configurable, Customizable UIs - facilitated by A2UI (agent optional)
- Developer tooling for A2UI at build time
- Patterns for A2UI caching, as config layer for static UI
- Designs and samples for full app composition
Multi-Agent Coordination
Enhanced support for multiple agents contributing to the same UI:
- Recommended agent composition patterns
- Conflict resolution strategies
- Shared surface management
Accessibility Features
First-class accessibility support:
- ARIA attribute generation
- Screen reader optimization
- Keyboard navigation standards
- Contrast and color guidance
Advanced UI Patterns
Support for more complex UI interactions:
- Drag and drop
- Gestures and animations
- 3D rendering
- AR/VR interfaces (exploratory)
Ecosystem Growth
- More framework integrations
- Third-party component libraries
- Agent marketplace integration
- Enterprise features and support
How to Influence the Roadmap
We welcome community input on priorities:
- Submit feedback form: Use this anonymous form to share your requirements and feedback
- Vote on Issues: Give π to GitHub issues you care about
- Propose Features: Open a discussion on GitHub (search for existing discussions first)
- Submit PRs: Build the features you need (search for existing PRs first)
- Join Discussions: Share your use cases and requirements (search for existing discussions first)
Planned Release Cycle
- Major versions (1.0, 2.0): Annual or when significant breaking changes are needed
- Minor versions (1.1, 1.2): Quarterly with new features
- Patch versions (1.1.1, 1.1.2): As needed for bug fixes
Versioning Policy
A2UI follows Semantic Versioning:
- MAJOR: Incompatible protocol changes
- MINOR: Backward-compatible functionality additions
- PATCH: Backward-compatible bug fixes
Stay Updated
- Watch the GitHub repository for updates
- Star the repo to show your support
- Follow releases to get notified of new versions
Last Updated: June 2026
Have questions about the roadmap? Start a discussion on GitHub.
Public Composer Composer Renderer Integration (`docs/public/composer/composer_renderer_integration.md`)
A2UI Composer Integration Manual
Background
The A2UI Composer has no knowledge or integration with any particular catalog or
renderer stack. In order to see the A2UI JSON rendered, it relies on a βRenderer
Application.β The A2UI Composer hosts the Renderer Application in an iframe and
communicates with it via postMessage. The iframe hosting the renderer
application has sandbox='allow-scripts allow-same-origin allow-forms', which
should not be a problem for simply rendering the A2UI JSON.
The Renderer Application is responsible for accepting A2UI JSON and using its
renderers to display the result.
The Bridge
To simplify the work of integrating with the A2UI Composer, a bridge has
been created. This is a small amount of Javascript code that the Renderer
Application incorporates, and coordinates all the communication between the A2UI
Composer and the Renderer Application.
To further simplify the integration, different framework-specific wrappers are
provided.
Examples
Check out the sample renderer apps:
They all serve the same version of the Basic catalog.
When running the hosted A2UI Composer,
you can see each of these renderer applications in action by going to the
Settings page and clicking the Renderer dropdown.
Using Angular
As an example, we'll walk through creating a Renderer application based on
Angular.
Add dependencies
First, add the core integration package to your project dependencies list:
yarn add a2ui-bridge @a2ui/web_core @a2ui/angularOf course, if youβre using a different package manager, follow the appropriate
steps.
Create the wrapper component
Create a new component that looks like this:
import {Component, inject} from '@angular/core';
import {SurfaceComponent} from '@a2ui/angular/v0_9';
import {A2uiSandboxConnection} from 'a2ui-bridge/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [SurfaceComponent],
template: `
<main class="sandbox-shell">
@if (sandbox.surfaceId()) {
<a2ui-v09-surface [surfaceId]="sandbox.surfaceId()" />
} @else {
<p style="padding: 24px; color: #666; font-family: sans-serif; text-align: center;">
Waiting for RENDER_A2UI payloads...
</p>
}
</main>
`,
})
export class AppComponent {
protected sandbox = inject(A2uiSandboxConnection);
}Note that contents of the @else can be modified as you see fit, but the rest
of the template should remain as-is.
Bootstrap your renderer application
Configure the standard Angular bootstrapping entrypoint file (src/main.ts),
passing your catalog classes dynamically to the sandbox provider mapping:
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {provideA2uiSandbox} from 'a2ui-bridge/angular';
import {BasicCatalog} from '@a2ui/angular/v0_9';
bootstrapApplication(AppComponent, {
providers: [
provideA2uiSandbox([BasicCatalog]), // Injects and exposes dynamic catalogs
],
}).catch((err) => console.error('A2UI Sandbox Bootstrap Failed:', err));Make sure to replace BasicCatalog with your catalog.
NOTE on change detection compatibility: The
provideA2uiSandboxhelper is
100% compatible with both standard Zone-based change detection (usingzone.js)
and Zoneless change detection (provideZonelessChangeDetection())
out-of-the-box.
Public Concepts Actions (`docs/public/concepts/actions.md`)
Handling User Actions
This guide explains how A2UI handles user interactions. Components use the action property to trigger either local Functions (executed on the renderer) or Events (dispatched to the agent). In addition, Data Model Synchronization ensures the agent always has access to the full UI state, enabling seamless multi-modal interactions like voice commands. This design enables highly responsive interfaces while maintaining a secure, restricted environment.
Action Architecture
Actions allow UI components to trigger behavior defined in the Action schema in common_types.json. Actions can trigger:
- Events: Dispatched to the Agent for processing (executed on Agent, e.g., clicking "Submit").
- Functions: Executed entirely on the renderer using
FunctionCall(executed on Renderer, e.g., opening a URL).
1. Functions (Local)
Functions execute immediate behavior on the renderer without a network round-trip. The agent is not informed of local function calls. They use the functionCall keyword.
{
"id": "help-btn",
"component": "Button",
"child": "help-text",
"action": {
"functionCall": {
"call": "openUrl",
"args": {"url": "https://a2ui.org/help"}
}
}
}Common uses for Functions include:
- Navigation: Opening a URL or switching tabs.
- Validation: Checking inputs before submission (see Checks below).
2. Events (Agent)
Events send data to the agent for processing. They use the event keyword.
Components like Button expose an action property. Here is how an Event is wired:
{
"id": "submit-btn",
"component": "Button",
"child": "btn-text",
"action": {
"event": {
"name": "submit_reservation",
"context": {
"time": {"path": "/reservationTime"},
"size": {"path": "/partySize"}
}
}
}
}name: A stable identifier for the agent to switch on.context: A map of key-value pairs. Values can be literal or use apathto pull from the current state of the data model.
NOTE: Context vs. Data Model: While the Data Model represents the entire state tree of a surface, the context in an action is effectively a hand-picked "view" or subset of that state. This simplifies the Agent's job by providing exactly the values needed for a specific event, without requiring the Agent to navigate a potentially large and complex data model.
Basic Catalog Function Validation (Checks)
The basic catalog defines a limited set of checks that can be performed on the renderer. Interactive components can define a list of checks (using the Checkable schema in common_types.json). For a Button, if any check fails, the button is automatically disabled on the renderer.
- UX Focus: Validation checks are designed to manage UI State (User Experience) by preventing invalid interactions before they happen. They are not a replacement for Data Integrity checks, which must still be performed on the agent.
This allows the UI to enforce requirements (like a non-empty field) before the user even tries to submit.
{
"id": "submit-button",
"component": "Button",
"child": "submit-text",
"checks": [
{
"condition": {
"call": "required",
"args": {"value": {"path": "/partySize"}}
},
"message": "Party size is required"
}
],
"action": {"event": {"name": "submit_booking"}}
}Local State Updates & The "Write" Contract
Before an Event is even dispatched, the renderer is already managing the state of the UI locally. A2UI defines a Read/Write Contract for all input components (like TextField, CheckBox, or Slider).
- Read (Model β View): When a component renders, it pulls its value from the bound
pathin the Data Model. - Write (View β Model): As soon as a user interacts (e.g., typing a character or clicking a checkbox), the renderer immediately writes the new value into the local Data Model.
This means the local model is always the source of truth for the UI's current state. This "View-to-Model" synchronization happens purely on the renderer. The data model is only sent to the agent when an event occurs (like a Button click).
IMPORTANT: Synchronous Updates: Local model updates are synchronous. This guarantees that the Data Model is fully updated before any Event resolves its context paths or a DataModelSync payload is packaged. There are no race conditions between typing and clicking; the "Write" is always committed first.
This local-first approach offers a significant Performance Benefit. Because synchronization is immediate and local, developers don't need to implement network debouncing or worry about latency jitters as a user types in a TextField. The network is completely protected from "UI noise" (like individual keystrokes) until the user is ready to dispatch a formal Event.
The Form Submission Pattern
This separation allows for a robust form submission pattern:
- Binding: A
TextFieldis bound to/reservationTime. - Interaction: The user types "7:00 PM". The local model at
/reservationTimeis updated instantly. - Submission: The user clicks a "Book" button. The button's Event resolves the
path: "/reservationTime"from the local model and sends the current value to the agent.
User Interaction Flow
When a user interacts with a component (e.g., clicks a button):
- Resolve: The renderer resolves all
pathreferences in thecontextagainst the local Data Model. - Construct: The renderer builds an
actionpayload conforming toclient_to_server.json. - Dispatch: The payload is sent via the chosen transport (e.g., A2A, WebSockets).
Example: The Action Payload (v0.9)
If a user clicks the button above with a data model containing {"reservationTime": "7:00 PM", "partySize": 4}, the renderer sends a message using the action key:
{
"version": "v0.9",
"action": {
"name": "submit_reservation",
"surfaceId": "booking-surface",
"sourceComponentId": "submit-btn",
"timestamp": "2026-02-25T10:40:00Z",
"context": {
"time": "7:00 PM",
"size": 4
}
}
}IMPORTANT: A Note on Versioning (v0.8 vs v0.9): In v0.8, the top-level payload key was userAction (e.g., {"userAction": {...}}). v0.9 transitioned to the simpler action key shown above. Standard protocol parsers expect the key corresponding to the version declared in the payload.
Agent Processing
The Agent (or an Orchestrator) receives this event and acts on it. In an agentic system, the agent usually converts the event into a hidden user query for the LLM.
Example Agent Processing (Python):
if action_name == "submit_reservation":
time = context.get("time")
size = context.get("size")
# Feed this to the LLM
query = f"User submitted a reservation for {size} people at {time}."
response = await llm.generate(query)Renderer-to-Agent Error Reporting
In addition to Events triggered by the user, the renderer can report system-level errors back to the agent using the error payload defined in client_to_server.json.
Validation Failures
If the agent sends A2UI JSON that violates the catalog schema or protocol rules, the renderer sends a VALIDATION_FAILED error. This is a critical feedback loop for agentic systems:
{
"version": "v0.9",
"error": {
"code": "VALIDATION_FAILED",
"surfaceId": "booking-surface",
"path": "/components/0/children",
"message": "Expected array of strings, got null."
}
}The agent can catch this error, apologize (or self-correct internally), and re-send the corrected UI.
Data Model Sync (v0.9)
A2UI v0.9 introduced a powerful "stateless" synchronization feature. This allows the renderer to automatically include the entire data model of a surface in the metadata of every message it sends to the agent.
Enabling Sync
Synchronization is requested by the agent during surface initialization. By setting sendDataModel: true in the createSurface message, the agent instructs the renderer to start the sync loop.
{
"version": "v0.9",
"createSurface": {
"surfaceId": "booking-surface",
"catalogId": "https://a2ui.org/catalogs/v1/basic.json",
"sendDataModel": true
}
}Sync on the Wire
When sync is enabled, the renderer does not send the data model as a separate message. Instead, it attaches it as metadata to the outgoing transport envelope (e.g., an A2A message).
In an A2A (Agent-to-Agent) binding, the data model is placed in an a2uiClientDataModel object within the envelope's metadata field.
Example A2A Envelope with Sync:
{
"parts": [{"text": "Submit the reservation"}],
"metadata": {
"a2uiClientDataModel": {
"version": "v0.9",
"surfaces": {
"booking-surface": {
"reservationTime": "7:00 PM",
"partySize": 4,
"notes": "Window seat preferred"
}
}
}
}
}Why use Data Model Sync?
- Simpler Wiring: You don't need to manually map every input field into a button's
contextproperty. The agent can simply inspect the metadata to see the current state of all fields. - Stateless Agents: The agent doesn't need to maintain local state for every user session; it receives the full current context with every single interaction.
- Verbal Shortcuts: Allows the user to trigger Events via voice or text (e.g., "okay submit") even without clicking a specific button. Since the agent receives the updated data model with the text message, it can process the request immediately.
Renderer Metadata & Capabilities
Before an agent can safely send a UI, the renderer must advertise which component catalogs it supports. This is handled via the a2uiClientCapabilities object.
Advertising Capabilities
Renderers include an a2uiClientCapabilities object in the metadata of their messages to the agent (e.g., in the metadata field of an A2A Message).
{
"v0.9": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"https://my-company.com/catalogs/v1/custom.json"
],
"inlineCatalogs": []
}
}supportedCatalogIds: An array of catalog URIs the renderer can render.inlineCatalogs: (Optional) For development or specialized environments, allows sending the full catalog schema inline.
Without this handshake, an agent cannot be certain that the renderer can handle the specific components being sent.
Transport & Encoding
A2UI is transport-agnostic, but it is most commonly used over A2A (Agent-to-Agent) or WebSockets. Understanding how the payload is wrapped is crucial for implementation.
A2A Encoding
In the standard A2A binding, A2UI messages are encoded as an A2A DataPart. To identify it as an A2UI payload, the part must be wrapped with specific metadata:
- mimeType:
application/a2ui+json
The data field of the DataPart contains a list of A2UI messages. This allows multiple updates (e.g., createSurface followed by updateComponents) to be sent in a single network packet.
NOTE: A2A Versioning: The use of a list in the data field was introduced in A2A v1.0. Earlier versions of the A2A protocol expect the data field to contain a single JSON object.
{
"kind": "data",
"metadata": {
"mimeType": "application/a2ui+json"
},
"data": [
{
"version": "v0.9",
"action": { ... }
}
]
}Security Considerations
A2UI is designed with secure, sandboxed communication as a core principle. Because the protocol relies on passing user state and interaction triggers over the network, it enforces strict boundaries on data visibility and execution.
Sandboxed Execution
A core selling point of A2UI is security through restriction. By prohibiting arbitrary code execution (like injecting raw JavaScript) from the agent, A2UI ensures that agents can only trigger pre-registered behaviors. The functionCall mechanism acts as a safe, sandboxed way for the agent to interact with the renderer's environment without exposing the user to malicious scripts.
Data Model Isolation and Orchestrator Routing
When sendDataModel: true is enabled, the renderer includes the surface's entire data model in outgoing messages. Developers must understand the visibility of this data:
- Point-to-Point Visibility: Only the backend receiving the transport envelope (the Agent that created the surface, or an intermediate Orchestrator) can read this payload.
- The Orchestrator's Responsibility: In a multi-agent architecture, a central Orchestrator often routes user intents to specialized sub-agents. The Orchestrator must enforce data isolation. It is responsible for parsing the
a2uiClientDataModel, identifying thesurfaceId, and ensuring that the data model is only passed to the specific sub-agent that owns that surface. Data from one agent's surface must never leak to another agent.
Orchestration & Routing
In multi-agent systems, a central Orchestrator often manages interactions between a user and several specialized sub-agents. A key challenge is ensuring that action messages from the renderer are routed back to the specific sub-agent that generated the UI surface.
The Surface Ownership Pattern
To handle routing in a multi-agent architecture, an orchestrator must maintain a mapping of each surfaceId to its owning sub-agent. The official Python SDK provides the A2uiSubagentMap utility to manage this mapping safely.
For a complete working example, see the orchestrator agent executor sample.
1. Mapping Ownership from Server Events
When a sub-agent emits an A2UI createSurface or deleteSurface the orchestrator intercepts the message and records the ownership in the session state using update_from_server_event.
from a2ui.adk import A2uiSubagentMap, SurfaceIdAlreadyExistsError
from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutorConfig
from google.adk.a2a.executor.config import ExecuteInterceptor
from google.adk.a2a.events import Event as A2AEvent
from google.adk.events.event import Event
from google.adk.sessions import SessionService, Session
async def after_event_save_surface_id(a2a_event: A2AEvent, event: Event, session_service: SessionService, session: Session):
for a2a_part in a2a_event.status.message.parts:
try:
await A2uiSubagentMap.update_from_server_event(
a2a_part,
event.author,
session_service,
session
)
except SurfaceIdAlreadyExistsError as e:
# Handle surface ID collision
pass
return a2a_event
config = A2aAgentExecutorConfig(
execute_interceptors=[
ExecuteInterceptor(after_event=after_event_save_surface_id)
]
)2. Routing Client Events
When the client renderer sends an A2UI action or error message back, the orchestrator looks up the surfaceId in the session state using get_subagent_name_for_client_event and routes the request to the correct sub-agent.
from a2ui.adk import A2uiSubagentMap
from google.adk.agents.llm_agent import LlmAgent
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.llm_request import LlmRequest
from google.adk.models.llm_response import LlmResponse
from google.genai import types as genai_types
from google.adk.agents.remote_a2a_agent import convert_genai_part_to_a2a_part
async def route_client_event(callback_context: CallbackContext, llm_request: LlmRequest):
a2a_part = convert_genai_part_to_a2a_part(llm_request.contents[-1].parts[-1]) # Assume response has a single a2a part
target_agent = await A2uiSubagentMap.get_subagent_name_for_client_event(
a2a_part,
callback_context.state
)
if target_agent:
# Programmatically trigger the planner's transfer_to_agent function
return LlmResponse(
content=genai_types.Content(
parts=[
genai_types.Part(
function_call=genai_types.FunctionCall(
name="transfer_to_agent",
args={"agent_name": target_agent},
)
)
]
)
)
orchestrator_agent = LlmAgent(
name="orchestrator",
before_model_callback=route_client_event,
# other configs ...
)This pattern ensures that the bidirectional communication loop remains intact and stateful for each feature area.
Preventing Data Leakage via Metadata Stripping
In a multi-agent environment, the a2uiClientDataModel object can contain the state for multiple surfaces owned by different sub-agents. To prevent sensitive data leakage, an orchestrator must strip the data model metadata to include only the surfaces owned by the target sub-agent.
You can use the strip_unowned_surfaces_from_data_model method in an outbound interceptor to mutate the data model dictionary in place.
from a2ui.adk import A2uiSubagentMap
from a2a.client.middleware import ClientCallInterceptor, ClientCallContext
from a2a.types import AgentCard
class A2UIMetadataInterceptor(ClientCallInterceptor):
async def intercept(self, request_payload: dict, agent_card: AgentCard, context: ClientCallContext):
message = request_payload.get("params", {}).get("message")
# Strip the data model to prevent data leakage
if data_model := message.get("metadata", {}).get("a2uiClientDataModel"):
await A2uiSubagentMap.strip_unowned_surfaces_from_data_model(
agent_card.name,
data_model,
context.state,
)
return request_payloadBy stripping the metadata, the orchestrator ensures that each sub-agent receives only the portion of the data model that it is authorized to see.
WARNING: Security Risk - State Scraping: If an orchestrator fails to strip the a2uiClientDataModel, a malicious or compromised sub-agent could read the state of other active surfaces. For example, a weather sub-agent could scrape sensitive data from a banking surface if the orchestrator leaks the entire multi-surface data model. Stripping is a mandatory security requirement for multi-agent systems.
Comprehensive Examples
1. Button Submit (Explicit Context)
This example shows a button that explicitly gathers the data it needs to send.
Component Definition:
{
"id": "submit-button",
"component": "Button",
"child": "submit-text",
"action": {
"event": {
"name": "submit_booking",
"context": {
"partySize": {"path": "/partySize"},
"reservationTime": {"path": "/reservationTime"}
}
}
}
}Resulting Action Payload:
The agent receives an action object with the partySize and reservationTime directly in the context field.
2. Verbal Submit (Data Model Sync)
In this scenario, the user doesn't click a button. Instead, they say "Okay, submit the form."
Initialization:
The agent created the surface with sendDataModel: true:
{
"version": "v0.9",
"createSurface": {
"surfaceId": "booking-surface",
"catalogId": "...",
"sendDataModel": true
}
}Renderer Transmission:
The renderer sends an A2A message containing the user's text and the data model in the metadata:
{
"parts": [{"text": "Okay, submit the form"}],
"metadata": {
"a2uiClientDataModel": {
"version": "v0.9",
"surfaces": {
"booking-surface": {
"partySize": 4,
"reservationTime": "7:00 PM"
}
}
}
}
}Agent Processing:
The agent sees the user's intent ("submit") and looks at the metadata to find the current values of partySize and reservationTime, allowing it to complete the task without further clarification.
Public Concepts Catalogs (`docs/public/concepts/catalogs.md`)
A2UI Catalogs
Overview
This guide defines the A2UI Catalog architecture and provides a roadmap for implementation. It explains the structure of catalog schemas, outlines strategies for using the pre-built "Basic Catalogβ versus defining your own application-specific catalog, and details the technical protocols for catalog negotiation, versioning, and runtime validation.
Catalog Schema
A catalog schema is a JSON Schema file outlining the components, functions, and themes that agents can use to define A2UI surfaces using server-driven UI. All A2UI JSON sent from the agent is validated against the chosen catalog.
Catalog JSON Schema is below
{
"Catalog": {
"type": "object",
"description": "A collection of component and function definitions.",
"properties": {
"catalogId": {
"type": "string",
"description": "Unique identifier for this catalog."
},
"components": {
"type": "object",
"description": "Definitions for UI components supported by this catalog.",
"additionalProperties": {
"$ref": "https://json-schema.org/draft/2020-12/schema"
}
},
"functions": {
"type": "array",
"description": "Definitions for functions supported by this catalog.",
"items": {
"$ref": "#/$defs/FunctionDefinition"
}
},
"theme": {
"title": "A2UI Theme",
"description": "A schema that defines a catalog of A2UI theme properties.",
"type": "object",
"additionalProperties": {
"$ref": "https://json-schema.org/draft/2020-12/schema"
}
}
},
"required": ["catalogId"],
"additionalProperties": false
}
}Catalog Strategy
Every A2UI surface is driven by a Catalog. A catalog is simply a JSON Schema file that tells the agent which components, functions, and themes are available for it to use.
Whether you are building a simple prototype or a complex production application, the requirement is the same: you must provide a catalog definition that the agent uses to express UI.
The Basic Catalog
To help developers get started quickly, the A2UI team maintains the Basic Catalog.
This is a pre-defined catalog file that contains a basic set of general-purpose components (Buttons, Inputs, Cards) and functions. It is not a special "type" of catalog; it is simply a version of a catalog that we have already written and have open source renderers for.
The basic catalog allows you to bootstrap an application or validate A2UI concepts without needing to write your own schema from scratch. It is intentionally sparse to remain easily implementable by different renderers.
Since A2UI is designed for LLMs to generate the UI at either design time or runtime, we do not think portability requires a standardized catalog across multiple clients; the LLM can interpret the catalog for each individual frontend.
See the A2UI v0.9 basic catalog
Defining Your Own Catalog
While the Basic Catalog is useful for starting out, most production applications will define their own catalog to reflect their specific design system.
By defining your own catalog, you restrict the agent to using exactly the components and visual language that exist in your application, rather than generic inputs or buttons. This catalog can be built entirely from scratch, or it can import definitions from the Basic Catalog to save time (e.g., using the Basic text definitions while defining your own unique Card component).
For simplicity we recommend building catalogs that directly reflect a client's design system rather than trying to map the Basic Catalog to it through an adapter. Since A2UI is designed for GenUI, we expect the LLM can interpret different catalogs for different clients.
See an example Rizzcharts catalog
Recommendations
| Usecase | Recommendation | Effort |
|---|---|---|
| Adding A2UI to a mature frontend | Define a catalog that mirrors your existing design system. | Medium |
| Adding A2UI to a new/greenfield app | Start with Basic Catalog, then evolve into your own catalog as the app evolves | Low (assuming renderer exists) |
Building a Catalog
A catalog is a JSON Schema file that conforms to the Catalog schema that defines the components, themes and functions an agent can use when building a surface.
Example: A Minimal Catalog
Here is a simple catalog defining a single component.
{
"$id": "https://github.com/.../hello_world/v1/catalog.json",
"catalogId": "https://github.com/.../hello_world/v1/catalog.json",
"components": {
"HelloWorldBanner": {
"type": "object",
"description": "A simple banner greeting.",
"properties": {
"message": {
"type": "string",
"description": "The banner text."
},
"backgroundColor": {
"type": "string",
"default": "#f0f0f0"
}
},
"required": ["message"]
}
}
}When the agent uses that catalog, it generates a payload strictly conforming to that structure:
[
{
"version": "v0.9",
"createSurface": {
"surfaceId": "hello-world-surface",
"catalogId": "https://github.com/.../hello_world/v1/catalog.json"
}
},
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "hello-world-surface",
"components": [
{
"id": "root",
"component": "HelloWorldBanner",
"message": "Hello, world! Welcome to your first catalog.",
"backgroundColor": "#4CAF50"
}
]
}
}
]Catalog Linking
A catalog's only external references are to the protocol's common_types.json and to peer catalogs: other catalogs that the agent or renderer already holds locally, because they are registered with it or supplied together with the referencing catalog. Peer catalogs are never fetched over the network.
A reference into a peer has the form #/components/<Name> (a whole component, usually inherited with allOf), #/components/<Name>/properties/ (one property's schema) or #/functions/<Name>/.... `` is the peer's catalogId or $id, or a path or file name that ends one of them:
{
"catalogId": "https://example.com/catalogs/branded/v1/catalog.json",
"components": {
"BrandedText": {
"type": "object",
"allOf": [
{"$ref": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json#/components/Text"}
],
"properties": {
"component": {"const": "BrandedText"},
"badge": {"$ref": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json#/components/Text/properties/text"}
},
"required": ["component", "badge"]
}
}
}The SDKs resolve such references at runtime against the other catalogs you register or pass in (linkCatalogs / link_catalogs, withPeerCatalogs / with_peer_catalogs; the agent SDKs and MessageProcessor link the catalogs they are given). Peers are matched in this order, and the first match wins:
common_types.jsonalways means the protocol's common types, and a document named exactlycatalog.jsonmeans the referencing catalog itself.- A peer whose
catalogIdor$idequals the document or ends with it as a path suffix. - A path ending in
/catalog.jsonthat no peer matched in step 2 means the referencing catalog itself. - A peer whose
$idorcatalogIdhas the same file name as the document. Onlycatalog.jsonitself is generic, somcp_catalog.jsonmatches a peer by file name.
A reference that matches no peer is a catalog error. Prefer full $ids, which always match exactly one peer. See Cross-Catalog References in the v1.0 specification for the normative rules.
You can also bundle external references into a freestanding catalog at build time. This catalog registration process, also called "Linking", is consolidated under a single, multi-platform Node.js script (register-catalogs.js).
This linking script is natively wrapped inside Xcode Build Phases (for iOS/macOS client builds) and Gradle tasks (for Android client builds) to compile, aggregate, and link static and dynamic schemas seamlessly during your application's build phase. Renderers that don't resolve peer catalogs at runtime need bundled catalogs.
Composition & Imports
You do not have to define everything from scratch. You can define a catalog which uses existing components from the basic or other catalogs, and one that reusing the existing rendering logic.
Example: Extending the Basic Catalog
This catalog imports all elements from the Basic Catalog and adds a new SuggestionChips component.
{
"$id": "https://github.com/.../hello_world_with_all_basic/v1/catalog.json",
"catalogId": "https://github.com/.../hello_world_with_all_basic/v1/catalog.json",
"components": {
"allOf": [
{"$ref": "basic_catalog_definition.json#/components"},
{
"SuggestionChips": {
"type": "object",
"description": "A list of suggested prompts",
"properties": {
"suggestions": {
"type": "array",
"description": "The suggested prompts."
}
},
"required": ["suggestions"]
}
}
]
}
}Make sure to link and resolve external references during compilation using your platform's Xcode Build Phase or Gradle task (running register-catalogs.js).
Example: Cherry-picking Components
This catalog imports only Text from the Basic Catalog to build a simple Popup surface.
{
"$id": "https://github.com/.../hello_world_with_some_basic/v1/catalog.json",
"catalogId": "https://github.com/.../hello_world_with_some_basic/v1/catalog.json",
"components": {
"allOf": [
{"$ref": "catalogs/basic/catalog.json#/components/Text"},
{
"Popup": {
"type": "object",
"description": "A modal overlay that displays an icon and text.",
"properties": {
"text": {"$ref": "common_types.json#/$defs/ComponentId"}
},
"required": ["text"]
}
}
]
}
}Make sure to link and resolve external references during compilation using your platform's Xcode Build Phase or Gradle task (running register-catalogs.js).
Implementing Renderers
Client renderers implement the catalog by mapping the schema definition to actual code.
First, define the component API in TypeScript matching your catalog schema:
// api.ts
import {ComponentApi} from '@a2ui/web_core/v0_9';
import {z} from 'zod';
export const HelloWorldBannerApi = {
name: 'HelloWorldBanner',
schema: z.object({
message: z.string(),
backgroundColor: z.string().default('#f0f0f0'),
}).strict(),
} satisfies ComponentApi;Next, implement the component extending CatalogComponent:
// hello_world_banner.ts
import {CatalogComponent} from '@a2ui/angular/v0_9';
import {Component, computed} from '@angular/core';
import {HelloWorldBannerApi} from './api';
@Component({
selector: 'hello-world-banner',
template: `
<div [style.background-color]="backgroundColor()">
<h2>Hello World Banner</h2>
<p>{{ message() }}</p>
</div>
`,
})
export class HelloWorldBanner extends CatalogComponent<typeof HelloWorldBannerApi> {
protected readonly message = computed(() => this.props()['message']?.value() || '');
protected readonly backgroundColor = computed(() => this.props()['backgroundColor']?.value() || '#f0f0f0');
}Finally, register your custom components in an AngularCatalog:
// catalog.ts
import {AngularCatalog, BASIC_COMPONENTS, BASIC_FUNCTIONS} from '@a2ui/angular/v0_9';
import {HelloWorldBanner} from './hello_world_banner';
import {HelloWorldBannerApi} from './api';
const customBannerComponent = {
...HelloWorldBannerApi,
component: HelloWorldBanner
};
export const MY_CATALOG = new AngularCatalog(
'https://github.com/.../hello_world/v1/catalog.json',
[...BASIC_COMPONENTS, customBannerComponent],
BASIC_FUNCTIONS
);You can see a working example of a client renderer in the Orchestrator demo.
[!NOTE]
The Orchestrator demo currently uses v0.8 APIs. For a v0.9 example of catalog registration, see the DemoCatalog in the Angular explorer.Additionally, for client-side functions, the client determines the function's execution boundary (such as
clientOnlystatus) at runtime by reading its configuration from the active catalog definition.
A2UI Catalog Negotiation
Because clients and agents can support multiple catalogs, they must agree on which catalog to use through a catalog negotiation handshake.
Step 1: Agent advertises its support catalogs (optional)
The agent may optionally advertise which catalogs it is capable of speaking (e.g., in the A2A Agent Card). This is informational; it helps the client know if the agent supports their specific features, but the client doesnβt have to use it.
Example of an A2A AgentCard advertising that the agent supports the basic and rizzcharts catalogs
{
"name": "Ecommerce Dashboard Agent",
"description": "This agent visualizes ecommerce data...",
"capabilities": {
"extensions": [
{
"uri": "https://a2ui.org/a2a-extension/a2ui/v0.8",
"description": "Provides agent driven UI using the A2UI JSON format.",
"params": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"https://github.com/.../rizzcharts_catalog_definition.json"
]
}
}
]
}
}Step 2: Client advertises its support catalogs (required)
The client sends a list of supportedCatalogIds to the Agent, ordered by preference, in the metadata of every message. This tells the agent exactly what the client is prepared to render right now.
Example of A2A message containing the supportedCatalogIds in metadata
{
"parts": [
{
"text": "What is the current status of my flight?"
}
],
"metadata": {
"a2uiClientCapabilities": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"https://github.com/.../rizzcharts_catalog_definition.json"
]
}
}
}Step 3: Agent Selection
When the agent creates a new surface, it selects the best match from the client's supportedCatalogIds list. This choice is locked for the lifetime of that surface. If no compatible catalog is found, the agent will not send a UI.
Example A2UI Message from the agent defining the catalog_id used in a surface
{
"createSurface": {
"surfaceId": "salesDashboard",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
}
}Catalog Naming & Versioning
A2UI component catalogs require versioning because catalog definitions are often built in at compile time, so any mismatch between what an agent generates and what a client can render can affect the UI.
CatalogId Naming Convention
The catalogId is a unique text identifier used for negotiation between the client and the agent.
- Format: While the
catalogIdis technically a string, the A2UI convention is to use a URI (e.g.,https://example.com/catalogs/mysurface/v1/catalog.json). - Purpose: We use URIs to make the ID globally unique and easy for human developers to inspect in a browser.
- No Runtime Fetching: This URI does not imply that the agent or client downloads the catalog at runtime. The catalog definition must be known to the agent and client beforehand (at compile/deploy time). The URI serves only as a stable identifier.
- JSON Schema Compatibility (
$idandcatalogId): Because A2UI catalogs are currently represented as JSON Schema documents, catalog definitions should include both$id(for JSON Schema tooling) andcatalogId(for A2UI SDKs and catalog negotiation), setting both fields to the same URI.
Versioning Guidelines
Catalogs should be treated like APIs where version skew between agents and renderers is expected. Catalogs must maintain forward and backward compatibility across versions so that agents, renderers, and templates can evolve without breaking active sessions or stored transcripts.
Catalog Schema Evolution Rules
To maintain backward and forward compatibility, the following rules apply:
- Additive Only: It's safe to add new components, functions, or optional properties.
- Deprecate Rather than Delete: Never delete existing components, functions, or properties. Mark them
deprecated: truealong with anx-deprecated-reasoninstead. Renderers should retain handling logic for deprecated components and properties to maintain backward compatibility for historical transcripts, cached states, and templates. - Type Invariance: Renderers may ignore fields that change the type of existing fields. Catalogs should avoid changing the data type of an existing field. Note that add enum to a string doesn't change the type and won't break anything since enums are expected to be open.
- Open Enums: Treat enum definitions as open so older renderers do not fail when new enum variants are introduced upstream. Catalogs declare enums normally, just the renderer implementation should handle unknown enum variants.
- Graceful Degradation: Renderers should provide a graceful fallback for unknown components instead of failing the entire view hierarchy or surface.
- Round-Trip Unknown Component/Field Preservation: Intermediary services such as orchestrators should preserve unknown properties when serializing/deserializing messages.
- Major Version Bumps: Reserve major catalog version bumps strictly for sweeping cleanups (e.g., removing long-deprecated fields) or fundamentally breaking structural changes.
Deprecating Catalog Properties
When deprecating an existing component, function, or property:
deprecated(boolean, optional): Standard JSON Schema annotation indicating that a component, function, or property is deprecated.x-deprecated-reason(string, optional): Human-readable explanation of why the entity is deprecated and what to use instead.
{
"TextBox": {
"type": "object",
"properties": {
"color": {
"type": "string",
"description": "Icon color specification.",
"deprecated": true,
"x-deprecated-reason": "Use style property to set color using CSS."
},
"style": {
"type": "string",
"description": "CSS properties such as color."
}
}
}
}Major Version Bumps with CatalogId
When introducing fundamentally breaking changes that necessitate a major version bump, include the version in the catalogId URI. This allows using A2UI catalog negotiation to support multiple versions simultaneously during a migration, ensuring zero downtime.
Recommended Pattern:
| Change Type | URI Example | Description |
|---|---|---|
| Current | .../rizzcharts/v1/catalog.json | Version 1.x. Supports all additive updates in the 1.x branch. |
| Breaking | .../rizzcharts/v2/catalog.json | A new schema introducing breaking structural changes. |
Handling Migrations
To upgrade a catalog across major versions without breaking active agents, use A2UI Catalog Negotiation:
- Client Update: The client updates its list of
supportedCatalogIdsto include both the old and new versions (e.g.,[".../v2/...", ".../v1/..."]). - Agent Update: Agents are rebuilt with the v2 schema. When they see the client supports v2, they prefer it.
- Legacy Support: Older agents that have not yet been rebuilt will continue to match against v1 in the client's list, ensuring they remain functional.
A2UI Schema Validation & Fallback
To ensure a stable user experience, A2UI employs a two-phase validation strategy. This "defense in depth" approach catches errors as early as possible while ensuring clients remain robust when facing unexpected payloads.
Two-Phase Validation
- Agent-Side (Pre-Send): Before transmitting any UI payload, the agent runtime validates the generated JSON against the catalog definition.
- Purpose: To catch hallucinated properties or malformed structures at the source.
- Outcome: If validation fails, the agent can attempt to fix or regenerate the A2UI JSON, or it can do graceful degradation such as falling back to text in a conversational app.
- Client-Side: Upon receiving the payload, the client library validates the JSON against its local definition of the catalog.
- Purpose: Security and stability. This ensures that the code executing on the user's device strictly conforms to the expected contract, protecting against version mismatches or compromised agent outputs.
- Outcome: Failures here are reported back to the agent using the βerrorβ client message
Graceful Degradation
Even if a payload passes schema validation, the renderer may encounter runtime issues (e.g., a missing asset, a component implementation not yet loaded, or a platform-specific limitation).
Clients should not crash when encountering these errors. Instead, they should employ Graceful Degradation:
- Unknown Components: If a component is recognized in the schema but not implemented in the renderer, render a "safe" fallback (e.g., a generic card with the component's debug name) or skip rendering that specific node entirely.
- Text Fallback: If the entire surface fails to render, display the raw text description (if available) or a generic error message: "This interface could not be displayed."
Client-to-Server Error Reporting
When the client detects a validation error or a runtime failure, it can report this back to the agent. This allows the agent system to log the failure for developers or adjust its future behavior.
The client sends a VALIDATION_FAILED event using the standard A2UI Client-to-Server Event Schema.
Example of client reporting a missing required field
{
"version": "v0.9",
"error": {
"code": "VALIDATION_FAILED",
"surfaceId": "flight-status-card-123",
"path": "/components/FlightCard/flightNumber",
"message": "Missing required property 'flightNumber' in component 'FlightCard'."
}
}Inline Catalogs
Inline catalogs sent by the client at runtime are supported but not recommended in production. More details about them can be found here.
Public Concepts Components (`docs/public/concepts/components.md`)
Components & Structure
A2UI uses an adjacency list model for component hierarchies. Instead of nested JSON trees, components are a flat list with ID references.
Why Flat Lists?
Traditional nested approach:
- LLM must generate perfect nesting in one pass
- Hard to update deeply nested components
- Difficult to stream incrementally
A2UI adjacency list:
- Flat structure, easy for LLMs to generate.
- Send components incrementally.
- Update any component by ID.
- Clear separation of structure and data.
The Adjacency List Model
=== "v0.8"
```json
{
"surfaceUpdate": {
"components": [
{
"id": "root",
"component": {
"Column": {
"children": { "explicitList": ["greeting", "buttons"] }
}
}
},
{
"id": "greeting",
"component": {
"Text": { "text": { "literalString": "Hello" } }
}
},
{
"id": "buttons",
"component": {
"Row": {
"children": { "explicitList": ["cancel-btn", "ok-btn"] }
}
}
},
{
"id": "cancel-btn",
"component": {
"Button": {
"child": "cancel-text",
"action": { "name": "cancel" }
}
}
},
{
"id": "cancel-text",
"component": {
"Text": { "text": { "literalString": "Cancel" } }
}
},
{
"id": "ok-btn",
"component": {
"Button": {
"child": "ok-text",
"action": { "name": "ok" }
}
}
},
{
"id": "ok-text",
"component": {
"Text": { "text": { "literalString": "OK" } }
}
}
]
}
}
```=== "v0.9 and later"
```json
{
"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"
}
]
}
}
```
v0.9 and later use a flatter component format: `"component": "Text"` instead of nested `{"Text": {...}}`, and children are simple arrays instead of `{"explicitList": [...]}`.Components reference children by ID, not by nesting.
Component Basics
Every component has:
- ID: Unique identifier (
"welcome") - Type: Component type (
Text,Button,Card) - Properties: Configuration specific to that type
=== "v0.8"
```json
{
"id": "welcome",
"component": {
"Text": {
"text": { "literalString": "Hello" },
"usageHint": "h1"
}
}
}
```=== "v0.9 and later"
```json
{
"id": "welcome",
"component": "Text",
"text": "Hello",
"variant": "h1"
}
```The Basic Catalog
To help developers get started quickly, the A2UI team maintains the Basic Catalog.
This is a pre-defined catalog file that contains a basic set of general-purpose components (Buttons, Inputs, Cards). It is not a special "type" of catalog; it is simply a version of a catalog that has open source renderers available.
For the complete component gallery with examples, see Component Reference.
Static vs. Dynamic Children
=== "v0.8"
**Static (`explicitList`)** - Fixed list of child IDs:
```json
{
"children": {
"explicitList": ["back-btn", "title", "menu-btn"]
}
}
```
**Dynamic (`template`)** - Generate children from data array:
```json
{
"children": {
"template": {
"dataBinding": "/items",
"componentId": "item-template"
}
}
}
```
For each item in `/items`, render the `item-template`. See [Data Binding](data-binding.md) for details.=== "v0.9 and later"
**Static** - Fixed list of child IDs:
```json
{
"children": ["back-btn", "title", "menu-btn"]
}
```
**Dynamic** - Generate children from data array:
```json
{
"children": {
"path": "/items",
"componentId": "item-template"
}
}
```
For each item in `/items`, render the `item-template`. See [Data Binding](data-binding.md) for details.Hydrating with Values
Components get their values two ways:
=== "v0.8"
- **Literal** - Fixed value: `{"text": {"literalString": "Welcome"}}`
- **Data-bound** - From data model: `{"text": {"path": "/user/name"}}`=== "v0.9 and later"
- **Literal** - Fixed value: `{"text": "Welcome"}`
- **Data-bound** - From data model: `{"text": {"path": "/user/name"}}`LLMs can generate components with literal values or bind them to data paths for dynamic content.
Composing Surfaces
Components compose into surfaces (widgets):
=== "v0.8"
1. LLM generates component definitions via `surfaceUpdate`
2. LLM populates data via `dataModelUpdate`
3. LLM signals render via `beginRendering`
4. Client renders all components as native widgets=== "v0.9 and later"
1. LLM creates a surface via `createSurface` (specifying catalog; in v1.0, can also include initial data model and components)
2. LLM generates component definitions via `updateComponents`
3. LLM populates data via `updateDataModel`
4. Client renders all components as native widgetsA surface is a complete, cohesive UI (form, dashboard, chat, etc.).
Incremental Updates
Incremental updates support the following operations:
- Add - Send new component definitions with new IDs
- Update - Send component definitions with existing ID and new properties
- Remove - Update parent's
childrenlist to exclude removed IDs
The flat structure makes all updates simple ID-based operations.
Defining Your Own Catalog
While the Basic Catalog is useful for starting out, most production applications will define their own catalog to reflect their specific design system.
By defining your own catalog, you restrict the agent to using exactly the components and visual language that exist in your application, rather than generic inputs or buttons.
See Defining Your Own Catalog Guide for implementation details.
Best Practices
- Descriptive IDs: Use
"user-profile-card"not"c1" - Shallow hierarchies: Avoid deep nesting
- Separate structure from content: Use data bindings, not literals
- Reuse with templates: One template, many instances via dynamic children
Public Concepts Data Binding (`docs/public/concepts/data-binding.md`)
Data Binding
Data binding connects UI components to application state using JSON Pointer paths (RFC 6901). It allows A2UI to efficiently define layouts for large arrays of data and to show updated content without regenerating it from scratch.
Structure vs. State
A2UI separates:
- UI Structure (Components): What the interface looks like
- Application State (Data Model): What data it displays
This enables:
- Reactive updates.
- Data-driven UIs.
- Reusable templates.
- Bidirectional binding.
The Data Model
Each surface has a JSON object holding state:
{
"user": {"name": "Alice", "email": "[email protected]"},
"cart": {
"items": [{"name": "Widget", "price": 9.99, "quantity": 2}],
"total": 19.98
}
}JSON Pointer Paths
Syntax:
/user/name- Object property/cart/items/0- Array index (zero-based)/cart/items/0/price- Nested path
Example:
{"user": {"name": "Alice"}, "items": ["Apple", "Banana"]}/user/nameβ"Alice"/items/0β"Apple"
Literal vs. Path Values
=== "v0.8"
**Literal (fixed):**
```json
{
"id": "title",
"component": {
"Text": {
"text": { "literalString": "Welcome" }
}
}
}
```
**Data-bound (reactive):**
```json
{
"id": "username",
"component": {
"Text": {
"text": { "path": "/user/name" }
}
}
}
```=== "v0.9 and later"
**Literal (fixed):**
```json
{
"id": "title",
"component": "Text",
"text": "Welcome"
}
```
**Data-bound (reactive):**
```json
{
"id": "username",
"component": "Text",
"text": { "path": "/user/name" }
}
```When /user/name changes from "Alice" to "Bob", the text automatically updates to "Bob".
Reactive Updates
Components bound to data paths automatically update when the data changes:
=== "v0.8"
```json
{
"id": "status",
"component": {
"Text": {
"text": {"path": "/order/status"}
}
}
}
```=== "v0.9 and later"
```json
{
"id": "status",
"component": "Text",
"text": {"path": "/order/status"}
}
```- Initial:
/order/status= "Processing..." β displays "Processing..." - Update: Send a data model update with
status: "Shipped"β displays "Shipped"
No component updates neededβjust data updates.
Dynamic Lists
Use templates to render arrays:
=== "v0.8"
```json
{
"id": "product-list",
"component": {
"Column": {
"children": {
"template": {
"dataBinding": "/products",
"componentId": "product-card"
}
}
}
}
}
```=== "v0.9 and later"
```json
{
"id": "product-list",
"component": "Column",
"children": {
"path": "/products",
"componentId": "product-card"
}
}
```Data:
{
"products": [
{"name": "Widget", "price": 9.99},
{"name": "Gadget", "price": 19.99}
]
}Result: Two cards rendered, one per product.
Scoped Paths
Inside a template, paths are scoped to the array item:
=== "v0.8"
```json
{
"id": "product-name",
"component": {
"Text": {
"text": {"path": "/name"}
}
}
}
```
- For `/products/0`, `/name` resolves to `/products/0/name` β "Widget"
- For `/products/1`, `/name` resolves to `/products/1/name` β "Gadget"=== "v0.9 and later"
```json
{
"id": "product-name",
"component": "Text",
"text": {"path": "name"}
}
```
- For `/products/0`, `name` resolves to `/products/0/name` β "Widget"
- For `/products/1`, `name` resolves to `/products/1/name` β "Gadget"Adding/removing items automatically updates the rendered components.
Input Bindings
Interactive components update the data model bidirectionally:
=== "v0.8"
| Component | Example | User Action | Data Update |
| ------------------ | ------------------------------------------- | ---------------- | -------------------------- |
| **TextField** | `{"text": {"path": "/form/name"}}` | Types "Alice" | `/form/name` = `"Alice"` |
| **CheckBox** | `{"value": {"path": "/form/agreed"}}` | Checks box | `/form/agreed` = `true` |
| **MultipleChoice** | `{"selections": {"path": "/form/country"}}` | Selects "Canada" | `/form/country` = `["ca"]` |=== "v0.9 and later"
| Component | Example | User Action | Data Update |
| ------------------ | ------------------------------------------- | ---------------- | -------------------------- |
| **TextField** | `{"value": {"path": "/form/name"}}` | Types "Alice" | `/form/name` = `"Alice"` |
| **CheckBox** | `{"value": {"path": "/form/agreed"}}` | Checks box | `/form/agreed` = `true` |
| **ChoicePicker** | `{"value": {"path": "/form/country"}}` | Selects "Canada" | `/form/country` = `["ca"]` |Best Practices
- Use granular updates: Update only changed paths.
=== "v0.8"
```json
{
"dataModelUpdate": {
"path": "/user",
"contents": [{"key": "name", "valueString": "Alice"}]
}
}
```=== "v0.9 and later"
```json
{
"version": "v0.9.1",
"updateDataModel": {
"surfaceId": "user_profile",
"path": "/user/name",
"value": "Alice"
}
}
```Organize by domain: Group related data.
json{"user": {...}, "cart": {...}, "ui": {...}}Pre-compute display values: Formats data (currency, dates) on the agent before sending.
json{"price": "$19.99"} // Not: {"price": 19.99}
Public Concepts Data Flow (`docs/public/concepts/data-flow.md`)
Data Flow
How messages flow from agents to UI.
Architecture
Agent (LLM) β A2UI Generator β Transport (SSE/WS/A2A)
β
Client (Stream Reader) β Message Parser β Renderer β Native UIMessage Format
A2UI defines a sequence of JSON messages that describe the UI. When streamed, these messages are often formatted as JSON Lines (JSONL), where each line is a complete JSON object.
=== "v0.8"
```jsonl
{
"surfaceUpdate": {
"surfaceId": "main",
"components": [...]
}
}
{
"dataModelUpdate": {
"surfaceId": "main",
"contents": [
{
"key": "user",
"valueMap": [
{ "key": "name", "valueString": "Alice" }
]
}
]
}
}
{
"beginRendering": {
"surfaceId": "main",
"root": "root-component"
}
}
```=== "v0.9"
```jsonl
{
"version": "v0.9",
"createSurface": {
"surfaceId": "main",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
}
}
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "main",
"components": [...]
}
}
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "main",
"path": "/user",
"value": { "name": "Alice" }
}
}
```Why this format?
A sequence of self-contained JSON objects is streaming-friendly, easy for LLMs to generate incrementally, and resilient to errors.
Lifecycle Example: Restaurant Booking
User: "Book a table for 2 tomorrow at 7pm"
=== "v0.8"
**1. Agent defines UI structure:**
```json
{
"surfaceUpdate": {
"surfaceId": "booking",
"components": [
{
"id": "root",
"component": {
"Column": {
"children": {
"explicitList": ["header", "guests-field", "submit-btn"]
}
}
}
},
{
"id": "header",
"component": {
"Text": {
"text": { "literalString": "Confirm Reservation" },
"usageHint": "h1"
}
}
},
{
"id": "guests-field",
"component": {
"TextField": {
"label": { "literalString": "Guests" },
"text": { "path": "/reservation/guests" }
}
}
},
{
"id": "submit-btn",
"component": {
"Button": {
"child": "submit-text",
"action": {
"name": "confirm",
"context": [
{ "key": "details", "value": { "path": "/reservation" } }
]
}
}
}
}
]
}
}
```
**2. Agent populates data:**
```json
{
"dataModelUpdate": {
"surfaceId": "booking",
"path": "/reservation",
"contents": [
{ "key": "datetime", "valueString": "2025-12-16T19:00:00Z" },
{ "key": "guests", "valueString": "2" }
]
}
}
```
**3. Agent signals render:**
```json
{
"beginRendering": {
"surfaceId": "booking",
"root": "root"
}
}
```
**4. User edits guests to "3"** β Client updates `/reservation/guests` automatically
**5. User clicks "Confirm"** β Client sends action:
```json
{
"userAction": {
"name": "confirm",
"surfaceId": "booking",
"context": {
"details": {
"datetime": "2025-12-16T19:00:00Z",
"guests": "3"
}
}
}
}
```
**6. Agent responds** β Updates UI or sends:
```json
{ "deleteSurface": { "surfaceId": "booking" } }
```=== "v0.9"
**1. Agent creates surface:**
```json
{
"version": "v0.9",
"createSurface": {
"surfaceId": "booking",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
}
}
```
**2. Agent defines UI structure:**
```json
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "booking",
"components": [
{
"id": "root",
"component": "Column",
"children": ["header", "guests-field", "submit-btn"]
},
{
"id": "header",
"component": "Text",
"text": "Confirm Reservation",
"variant": "h1"
},
{
"id": "guests-field",
"component": "TextField",
"label": "Guests",
"value": { "path": "/reservation/guests" }
},
{
"id": "submit-btn",
"component": "Button",
"child": "submit-text",
"variant": "primary",
"action": {
"event": {
"name": "confirm",
"context": {
"details": { "path": "/reservation" }
}
}
}
}
]
}
}
```
**3. Agent populates data:**
```json
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "booking",
"path": "/reservation",
"value": {
"datetime": "2025-12-16T19:00:00Z",
"guests": "2"
}
}
}
```
**4. User edits guests to "3"** β Client updates `/reservation/guests` automatically
**5. User clicks "Confirm"** β Client sends action:
```json
{
"version": "v0.9",
"action": {
"name": "confirm",
"surfaceId": "booking",
"context": {
"details": {
"datetime": "2025-12-16T19:00:00Z",
"guests": "3"
}
}
}
}
```
**6. Agent responds** β Updates UI or sends:
```json
{
"version": "v0.9",
"deleteSurface": { "surfaceId": "booking" }
}
```Transport Options
A2UI is transport-agnostic β any mechanism that can deliver JSON messages works:
- A2A Protocol: Standardized agent-to-agent communication, also used for agent-to-UI delivery
- AG-UI: Bidirectional, real-time agent-UI protocol
- REST / HTTP: Simple request-response or Server-Sent Events (SSE) for one-way streaming
- WebSocket: Persistent bidirectional connection, ideal for real-time updates and user actions
- Any other transport: gRPC, message queues, custom protocols β if it can carry JSON, it works
See transports for implementation details.
Progressive Rendering
Instead of waiting for the entire response to be generated before showing anything to the user, chunks of the response can be streamed to the client as they are generated and progressively rendered.
Users see UI building in real-time instead of staring at a spinner.
Error Handling
The system handles errors as follows:
- Malformed messages: Skip and continue, or send error back to agent for correction.
- Network interruptions: Display error state, reconnect, agent resends or resumes.
Performance
To optimize performance:
- Batching: Buffer updates for 16ms, batch render together.
- Diffing: Compare old/new components, update only changed properties.
- Granular updates: Update
/user/namenot entire/model.
Public Concepts Glossary (`docs/public/concepts/glossary.md`)
Glossary
A2UI protocol, between renderer and agent:
The A2UI protocol defines a conversation between an agent and a renderer:
- The renderer sends its UI capabilities as a set of A2UI catalogs, with instructions on how to use them.
- The agent then repeats a loop:
- Sends UI to render and functions to call, based on the catalog it received
- Receives user input from the renderer
- Updates the data shown in the UI
Agent and model:
To produce responses for the renderer, the agent sends requests to a model. The model can be:
- a remote or local LLM, in most cases
- another agent, in more complex orchestrations
- a deterministic engine, usually for testing
Interaction overview:
sequenceDiagram
participant Renderer
participant Agent
participant Model
Renderer->>Agent: Catalog & instructions
loop Agentic flow
Renderer->>Agent: User input
Agent->>Model: Context + User input
Model->>Agent: Response
Agent->>Agent: Parse response
Agent->>Renderer: Data + UI Updates. Function calls.
endA2UI SDKs:
The A2UI team provides two SDKs for a number of languages and frameworks:
- Agent SDK: sets up the agent side of the interaction with the renderer and the model.
- Framework adapter SDK (aka renderer SDK): sets up the renderer side of the interaction with the agent and handles incoming messages.
Terms in this glossary:
The terms below are grouped into three categories:
- A2UI protocol terms: interaction between the renderer and the agent.
- A2UI agent terms: agent architecture and the Agent SDK.
- Generative UI terms: general generative UI concepts.
A2UI protocol terms
Terms required by the A2UI protocol.
A2UI agent and A2UI renderer
The protocol is designed for AI-powered agents, but it works with deterministic agents as well. For example, an agent may return a pre-canned A2UI UI.
If the agent is stateless, or does not guarantee that it preserves the catalog, the renderer should send the catalog with every message.
Some agents use a predefined catalog, so renderers must either support that catalog or use an adapter.
GenUI component
A UI component that the agent is allowed to use. Examples: date picker, carousel, button, hotel selector.
Catalog
- Itemized renderer capabilities:
- List of components that the agent can use to generate UI
- List of functions that can be called by the renderer or the agent
- Styles and themes
- An explanation of how to use those capabilities.
Depending on the use case, catalog components can be more or less domain-specific:
Less specific:
Basic UI primitives like buttons, labels, rows, columns, option-selectors and so on.
More specific:
Components like HotelCheckout or FlightSelector.
Basic catalog
A catalog maintained by the A2UI team for getting started with A2UI quickly.
See the basic catalog.
Catalog transformer
A rule set that filters, adapts, or mutates a pristine catalog before system prompt instructions are generated or payload validation schemas are compiled.
Why catalog transformers are needed
A catalog specifies every component and function that a renderer supports, but agents often need to prune or restrict it:
- Context window optimization: A catalog can define dozens of components and functions. Injecting all of their JSON schemas into a model's system prompt costs context window tokens, adds prompt latency, and raises inference costs. A pruning transformer keeps only the components that the agent's task needs.
- Capability guardrails: Some workflows or security roles need interactive features restricted. For example, a guest mode may allow display cards and text, but no form inputs or administrative components.
- Model signature reduction: Pruning function rules and validation checks produces compact prompt instructions for small models.
Example catalog transformers
ComponentPruningTransformer: a utility class that filters the catalog based on a list of allowed component names.FunctionPruningTransformer: a utility class that filters the catalog based on a list of allowed function names.
Capabilities object
The set of catalogs that are available for use. An agent or a renderer advertises what it supports through this set, so the other side knows what it may send.
For example, if a renderer supports Basic, Hotels and Flights catalogs, it will advertise them in its capabilities object.
A capabilities object is exchanged through transport metadata or an initialization payload, not as an A2UI message. The exact placement is transport-specific.
The objects are named differently in earlier versions of the protocol: v0.9 and v0.9.1 call them client and server capabilities, and v0.8 defines only a client-side schema.
Surface
An area of UI, built by the A2UI agent and managed by the A2UI renderer, that consists of a number of components. Surfaces cannot nest.
A2UI message
A message between the agent and the renderer.
Because the protocol supports streaming, a message can be finished (fully delivered) or unfinished (partially delivered). A finished message is either completed (delivered successfully) or interrupted (delivery stopped because of a technical problem).
See the data flow guide.
Agent turn
The set of messages that the agent sends before it starts waiting for user input.
Data model
An observable, hierarchical, JSON-like object, shared between the renderer and the agent and updatable by both. Each surface has its own data model.
Components can bind to nodes of the data model and update automatically when those values change.
The data model gives two-way synchronization: it captures user interactions into a state object that is sent to the agent, and it lets the agent push data updates back to the UI.
See the data binding guide.
Data reference
In a component definition, a reference to a data element, resolved either by a path in the data model or by value.
See the example in the basic catalog.
Function
A named, catalog-defined operation with a declared argument signature and return type. Functions execute logic without sending raw executable code between the agent and renderer.
Functions are defined in catalogs and can execute on either the renderer or the agent:
- Renderer function: A function executed on the renderer. Renderer functions handle client-side tasks such as formatting values, validating input fields, navigating, or controlling local UI state. The agent can also request the renderer to execute a function (for example, to display a notification or trigger device feedback).
- Agent function: A function executed on the agent. When the UI requires server-side logic (such as checking inventory, querying a database, or verifying credentials), the renderer requests the agent to execute the function and return the result.
An A2UI function is not the same as an LLM tool:
| Feature | A2UI function | LLM tool invocation |
|---|---|---|
| Executor | A2UI renderer (for renderer functions) or agent (for agent functions). | Model host or agent environment outside the UI. |
| Timing | During UI rendering, on user interaction, or as a request between turns. | Before or during model response generation. |
| Purpose | UI logic: validation, formatting, client actions, or app operations. | Model reasoning, web search, database retrieval. |
| Definition | Declared in an A2UI catalog with argument and return signatures. | Declared in a tool definition passed to the model. |
| State access | Scoped to surface data context, local UI state, or catalog methods. | Scoped to external tools, APIs, and services. |
For details on how functions are routed and executed between the agent and renderer, see Functions in A2UI in the protocol specification.
Function call
An invocation of a catalog function with a set of arguments.
Function calls appear in several contexts:
- Dynamic value bindings: The renderer calls a renderer function to calculate a component property value dynamically (for example, formatting a date or currency).
- Validation rules: The renderer calls a renderer function to validate user input before submission.
- Component interaction triggers: The renderer calls a renderer function when a user interacts with a component (for example, opening a URL).
- Remote procedure calls (RPCs): The agent requests the renderer to execute a renderer function, or the renderer requests the agent to execute an agent function.
Function calls execute locally when the target function is implemented in the caller's environment. When the function resides on the other side of the agent-renderer boundary, the call is communicated over the protocol. In architectures where the agent and renderer run within the same process (such as a standalone desktop application), these calls can execute in-process without network transport.
Action
An event triggered by a user interaction with a UI component that is dispatched from the renderer to the agent.
An action informs the agent that an interaction occurred (such as clicking a button or selecting an option). It conveys the user's intent along with an action name, relevant values extracted from the renderer data model, and an optional description of the action.
Actions represent user intent communicated back to the agent to drive conversational turns or business logic. In contrast, when a component interaction triggers behavior directly without notifying the agent (such as opening a URL or running a client-side calculation), it performs a function call rather than dispatching an action.
See the detailed guide on actions.
A2UI agent terms
Terms used to describe agent architecture and the Agent SDK.
A2UI tag
In model-to-agent messages, a delimiter tag such as <a2ui-json> or <a2ui> that bounds an A2UI payload block inside the model's text output.
A single model turn can mix conversational text and structured UI blocks, so A2UI tags mark where each UI block starts and ends.
Inference format
The format of the structured UI blocks in model-to-agent messages, for example JSON, Express DSL syntax, or Elemental HTML tags.
Phases of parsing of model-to-agent responses
Tag unwrapping:
The first phase of response parsing (unwrap), where the parser scans the model response for opening and closing A2UI tags, and separates conversational text (e.g., "Here is your summary:") from the enclosed raw UI blocks.
Compilation:
The second phase of response parsing (compile), where the raw UI blocks extracted during unwrapping (standard JSON, Express DSL syntax, or Elemental HTML tags) are parsed, decompiled, and transformed into standard A2UI protocol payloads such as createSurface and updateDataModel.
Agent architecture
An A2UI agent can be built in several ways:
Same-process or server-side:
The agent and the renderer may live in one process of a client side application. Example: a desktop Flutter application.
Or the renderer may live on the box that displays the UI, and the agent on another box (server).
Orchestrator agent:
A central orchestrator manages interactions between a user and several specialized sub-agents. The orchestrator can be in the same process or on the server.
Pulling / pushing:
An agent can wait for messages and requests from the renderer, or push them to it.
Stateful / stateless:
Agents can preserve state or be stateless.
Mixed with other protocols:
A2UI can be used in combination with other protocols. For example, an agent may be an MCP and/or A2A server.
Something else:
Custom variations of the options above are also possible.
Recommended code organization
Agent and renderer functionality consists of layers that can be developed separately and reused:
Core library:
Set of primitives needed to describe a catalog and to interact with the agent.
For example, see the JavaScript web core library.
Catalog schema:
Definition of a catalog in the form of JSON.
For example, see the basic catalog schema.
Agent SDK:
Code that implements the agent's interaction with the model and the renderer.
Framework adapter (or renderer SDK):
Code that runs the agent's instructions in a concrete framework. For example:
- JavaScript core and catalogs may be adapted to Angular, Electron, React and Lit frameworks.
- Dart core and catalogs may be adapted to Flutter and Jaspr frameworks.
See the Angular adapter.
Catalog implementation:
Implementation of the catalog schema for a framework.
For example:
flowchart TD;
cimpl-->fadapter("Framework<br>Adapter");
cimpl("Catalog<br>Implementation")-->cschema("Catalog<br>Schema");
cschema-->core("Core<br>Library");
agentsdk("Agent SDK")-->cschema;
agentsdk-->core;
fadapter-->core;
renderer-->fadapter;
renderer("Renderer")-->cimpl;
agent("Agent")-->agentsdk;Generative UI terms
Terms that the A2UI protocol does not require, but that are common in the context of generative UI.
Known patterns of GenUI
Chat:
Pieces of generated UI appear one by one, sorted by time, in a vertically scrollable area, mixed with user input.
Canvas:
A space for collaboration with an agent.
Dashboard:
Pieces of generated UI are organized by meaning rather than by time, and stay pinned where the user expects to see them.
Wizard:
Pieces of generated UI are shown one by one, to collect the information needed for a certain task.
NoAI information
Information categorized as not accessible by AI, for example credit card details.
Application owners decide which information is not accessible by AI, and the answer is different in different contexts. For example, in some products medical history should never go to the AI, while in others the AI relies on medical history to help with diagnostics.
The term matters in the GenUI context, because end users want to clearly see which of their input may go to the AI and which may not.
Public Concepts Transports (`docs/public/concepts/transports.md`)
Transports (Message Passing)
Transports deliver A2UI messages from agents to clients. A2UI is transport-agnostic: use any method that can send JSON.
The actual component rendering is done by the renderer,
and the agents are responsible for generating the A2UI messages.
Getting the messages from the agent to the client is the job of the transport.
How It Works
Agent β Transport β Client RendererA2UI defines a sequence of JSON messages. The transport layer is responsible for delivering this sequence from the agent to the client. A common transport mechanism is a stream using a format like JSON Lines (JSONL), where each line is a single A2UI message.
Available Transports
| Transport | Status | Use Case |
|---|---|---|
| A2A Protocol | β Stable | Multi-agent systems, enterprise meshes |
| AG-UI | β Stable | Full stack React, Vue, Angular applications (CopilotKit) |
| REST API | π Planned | Simple HTTP endpoints |
| WebSockets | π‘ Proposed | Real-time bidirectional |
| SSE (Server-Sent Events) | π‘ Proposed | Web streaming |
A2A Protocol
The Agent2Agent (A2A) protocol provides secure,
standardized agent communication. An A2A extension provides easy integration with A2UI.
Benefits:
- Security and authentication built-in.
- Bindings for many message formats, auth, and transport protocols.
- Clean separation of concerns.
If you are using A2A, this should be nearly automatic.
TODO: Add a detailed guide.
See: A2A Extension Specification
AG-UI
AG-UI translates A2UI messages to AG-UI events and handles transport and state sync automatically. It is commonly used for full stack React, Vue, and Angular applications. CopilotKit is AG-UI's creator and primary consumer.
See: Use A2UI with Any Agent Framework (Using AG-UI): Set up CopilotKit with your agent framework of choice and enable A2UI rendering.
Custom Transports
You can use any transport that sends JSON:
HTTP/REST:
// TODO: Add an exampleWebSockets:
// TODO: Add an exampleServer-Sent Events:
// TODO: Add an exampleNext Steps
- A2A Protocol Docs: Learn about A2A
- A2A Extension Spec: A2UI + A2A details
Public Ecosystem A2ui In The World (`docs/public/ecosystem/a2ui-in-the-world.md`)
A2UI in the World
A2UI is being adopted by teams at Google and partner organizations to build the next generation of agent-driven applications. Here are real-world examples of where A2UI is making an impact.
Production Deployments
Google Opal: AI Mini-Apps for Everyone
Opal enables hundreds of thousands of people to build, edit, and share AI mini-apps using natural languageβno coding required.
How Opal uses A2UI:
The Opal team at Google has been a core contributor to A2UI from the beginning. They use A2UI to power the dynamic, generative UI system that makes Opal's AI mini-apps possible.
- Rapid prototyping: Build and test new UI patterns quickly.
- User-generated apps: Anyone can create apps with custom UIs.
- Dynamic interfaces: UIs adapt to each use case automatically.
"A2UI is foundational to our work. It gives us the flexibility to let the AI drive the user experience in novel ways, without being constrained by a fixed front-end. Its declarative nature and focus on security allow us to experiment quickly and safely."
β Dimitri Glazkov, Principal Engineer, Opal Team
Learn more: opal.google
Gemini Enterprise: Custom Agents for Business
Gemini Enterprise enables businesses to build powerful, custom AI agents tailored to their specific workflows and data.
How Gemini Enterprise uses A2UI:
A2UI is being integrated to allow enterprise agents to render rich, interactive UIs within business applicationsβgoing beyond simple text responses to guide employees through complex workflows.
- Data entry forms: AI-generated forms for structured data collection.
- Approval dashboards: Dynamic UIs for review and approval processes.
- Workflow automation: Step-by-step interfaces for complex tasks.
- Custom enterprise UIs: Tailored interfaces for industry-specific needs.
"Our customers need their agents to do more than just answer questions; they need them to guide employees through complex workflows. A2UI will allow developers building on Gemini Enterprise to have their agents generate the dynamic, custom UIs needed for any task, from data entry forms to approval dashboards, dramatically accelerating workflow automation."
β Fred Jabbour, Product Manager, Gemini Enterprise
Note: A2UI rendering in Gemini Enterprise currently requires self-serve agents registered using the Agent-to-Agent (A2A) path (
a2aAgentDefinition). Managed agents deployed on Vertex AI Agent Engine and registered using theadkAgentDefinitionpath do not currently support A2UI rendering.
Learn more: Gemini Enterprise
Flutter GenUI SDK: Generative UI for Mobile
The Flutter GenUI SDK brings dynamic, AI-generated UIs to Flutter applications across mobile, desktop, and web.
How GenUI uses A2UI:
GenUI SDK uses A2UI as the underlying protocol for communication between server-side agents and Flutter applications. When you use GenUI, you're using A2UI under the covers.
- Cross-platform support: Same agent works on iOS, Android, web, desktop.
- Native performance: Flutter widgets rendered natively on each platform.
- Brand consistency: UIs match your app's design system.
- Server-driven UI: Agents control what's shown without app updates.
"Our developers choose Flutter because it lets them quickly create expressive, brand-rich, custom design systems that feel great on every platform. A2UI was a great fit for Flutter's GenUI SDK because it ensures that every user, on every platform, gets a high quality native feeling experience."
β Vijay Menon, Engineering Director, Dart & Flutter
Try it:
- GenUI Documentation
- Getting Started Video
- Verdure Example (client-server A2UI sample)
Google ADK: Agent Development Kit
The Agent Development Kit (ADK) is Google's open-source framework for building and deploying AI agents. The built-in developer UI, ADK Web, includes native A2UI rendering.
How ADK uses A2UI:
ADK integrated the A2UI v0.8 basic catalog to automatically render spec-compliant agent parts as native UI components directly in the chat. ADK also handles A2UIβA2A message conversion, so agents built with ADK can send rich UI to any A2UI-capable client.
- Built-in rendering: ADK Web renders A2UI components natively in the dev UI.
- A2A integration: A2UI messages are converted between A2A DataPart metadata and ADK events.
- Agent SDK: The A2UI Python agent SDK provides an ADK extension for generating A2UI from agents.
Try it:
- ADK Documentation
- ADK Web (developer UI with A2UI support)
- Agent Development Guide (building A2UI agents with ADK)
Partner Integrations
AG-UI / CopilotKit: Full-Stack Agentic Framework
AG-UI provides the protocol and CopilotKit provides the primary full-stack framework for building agentic applications, with day-zero A2UI compatibility.
How they work together:
AG-UI excels at creating high-bandwidth connections between custom frontends and their dedicated agents. By adding A2UI support, developers get the best of both worlds:
- State synchronization: AG-UI handles app state and chat history.
- A2UI rendering: Render dynamic UIs from third-party agents.
- Multi-agent support: Coordinate UIs from multiple agents.
- Framework integrations: React, Vue, Angular, and other app surfaces through CopilotKit.
"AG-UI excels at creating a high-bandwidth connection between a custom-built front-end and its dedicated agent. By adding support for A2UI, we're giving developers the best of both worlds. They can now build rich, state-synced applications that can also render dynamic UIs from third-party agents via A2UI. It's a perfect match for a multi-agent world."
β Atai Barkai, Founder of CopilotKit and AG-UI
Learn more:
AG2: Multi-Agent Framework with Native A2UI
AG2 is a popular multi-agent framework, offering advanced orchestration of agents. Its A2UIAgent is a reference agent with native A2UI support, enabling AG2 agents to generate rich, interactive UI served over both A2A and AG-UI.
How AG2 uses A2UI:
A2UIAgent extends AG2's ConversableAgent with built-in A2UI capabilities β prompt engineering, schema validation with retry, and action handling β so developers can add generative UI to their agents without custom rendering code.
- Validated output: Built-in schema validation and retry ensures reliable A2UI generation.
- Dual transport: Serves the same UI over A2A (JSON-RPC) and AG-UI (SSE).
- Cross-platform: One agent serves web, desktop, and mobile clients.
- Custom catalogs: Extend the component catalog with domain-specific components.
"A2UIAgent brings the A2UI protocol to AG2, enabling agent expression with dynamic, rich, and interactive UIs. Reliable, client-agnostic rendering means our developers spend less time on integration and more time building great experiences."
β Mark Sze, Founding Engineer, AG2
Learn more:
- A2UIAgent Documentation
- Technical Deep Dive β Building A2UI agents with AG2
- A2UIAgent + Flutter Example β A2UIAgent served over A2A to a Flutter GenUI client
- AG2
Google's AI-Powered Products
As Google adopts AI across the company, A2UI provides a standardized way for AI agents to exchange user interfaces, not just text.
Internal agent adoption:
- Multi-agent workflows: Multiple agents contribute to the same surface.
- Remote agent support: Agents running on different services can provide UI.
- Standardization: Common protocol across teams reduces integration overhead.
- External exposure: Internal agents can be easily exposed externally (e.g., Gemini Enterprise).
"Much like A2A lets any agent talk to another agent regardless of platform, A2UI standardizes the user interface layer and supports remote agent use cases through an orchestrator. This has been incredibly powerful for internal teams, allowing them to rapidly develop agents where rich user interfaces are the norm, not the exception. As Google pushes further into generative UI, A2UI provides a perfect platform for server-driven UI that renders on any client."
β James Wren, Senior Staff Engineer, AI Powered Google
Community Projects
The A2UI community is building exciting projects:
Open Source Examples
Restaurant Finder (samples/agent/adk/restaurant_finder)
- Table reservation with dynamic forms
- Gemini-powered agent
- Full source code available
Component Gallery (samples/client/angular - gallery mode)
- Interactive showcase of all components
- Live examples with code
- Great for learning
Third-Party Integrations
- json-render β Vercel's React library for rendering A2UI component catalogs via Zod schemas. See json-render vs. A2UI comparison.
- OpenClaw Canvas β OpenClaw uses A2UI to render agent-generated UI on connected devices via its canvas system. See architecture overview.
Community Contributions
Have you built something with A2UI? Share it with the community!
Next Steps
For more information, see the following resources:
- Quickstart Guide - Try the demo.
- Agent Development - Build an agent.
- Client Setup - Integrate a renderer.
- Community - Join the community.
Using A2UI in production? Share your story on GitHub Discussions.
Public Ecosystem Community (`docs/public/ecosystem/community.md`)
Community
Welcome to the A2UI community! The project is building the future of agent-driven interfaces together.
Get Involved
A2UI is an open-source project licensed under Apache 2.0. Contributions are welcome from developers, researchers, and anyone interested in advancing agentic user interfaces.
Community Showcase
Built something with A2UI? Show it off!
The best way to share your work is to post in GitHub Discussions with a short description, a link to your code, and a demo video (4 minutes or less). Notable projects are featured in the docs and release notes.
Community-built renderers are listed on the Ecosystem Renderers page β check there for community implementations and instructions for adding your own.
Official samples in the repo are a great reference for what good A2UI projects look like:
Project Partners
A2UI is developed in collaboration with Google Opal, Flutter (GenUI SDK), Gemini Enterprise, AG-UI / CopilotKit, and the A2A team. For full details and case studies, see A2UI in the World.
If you have made significant contributions to A2UI, please submit your name to this list.
Code of Conduct
A2UI is committed to providing a welcoming and inclusive environment for everyone. All participants are expected to:
- Be respectful and considerate.
- Welcome newcomers and help them get started.
- Focus on what's best for the community.
- Show empathy towards others.
Unacceptable behavior will not be tolerated. Report concerns to the project maintainers.
Recognition
All contributions are appreciated! Contributors are recognized in:
- GitHub's contributor graph.
- Release notes.
- Project acknowledgments.
Significant contributors may be invited to join the project's maintainer team.
Stay Updated
To stay informed about project updates:
- Watch the GitHub repo for notifications.
- Star the repo to bookmark and show support.
- Follow releases to get notified of new versions.
Ways to Contribute
- Report Issues: Found a bug? Open an issue.
- Build Renderers: See the roadmap for planned frameworks.
- Share Examples: Post with
#A2UIon X/LinkedIn, start a discussion. - Improve Docs: PRs welcome in the
docs/directory.
Questions?
If you have questions:
- Check the documentation.
- Search GitHub Discussions.
- Ask in GitHub Issues.
Thank you for being part of the A2UI community!
Public Ecosystem Renderers (`docs/public/ecosystem/renderers.md`)
Ecosystem Renderers
Community and third-party A2UI renderer implementations.
NOTE
These renderers are maintained by their respective authors, not the A2UI team. Check each project for compatibility, version support, and maintenance status.
TIP
Looking for the official A2UI React renderer? See@a2ui/reactβ the core A2UI React renderer maintained by the A2UI team.
Community Renderers
| Renderer | Platform | v0.8 | v0.9 | v1.0 | Activity | Links |
|---|---|---|---|---|---|---|
easyops-cn/a2ui-sdk (@a2ui-sdk/react) |
React (Web) | β | β | β | GitHub Β· npm Β· Docs | |
| lmee/A2UI-Android | Android (Compose) | β | β | β | GitHub | |
| sivamrudram-eng/a2ui-react-native | React Native | β | β | β | GitHub | |
| zhama/a2ui | React (Web) | β | β | β | β | npm |
| jem-computer/A2UI-react | React (Web) | β | β | β | GitHub | |
| BBC6BAE9/a2ui-swift | Apple native (SwiftUI, UIKit, AppKit) | β | β | β | GitHub | |
| a2ui-vue | Vue (Web) | β | β | β | GitHub Β· npm Β· Docs | |
| AGenUI/AGenUI | iOS, Android, HarmonyOS | β | β | β | GitHub Β· Official Website | |
lynx-family/lynx-stack (@lynx-js/genui/a2ui) |
Lynx (Mobile, Web, Desktop) | β | β | β | GitHub Β· npm Β· Docs | |
BoteAI/a2ui (@boteai/a2ui-render) |
React (Web) | β | β | β | GitHub Β· npm | |
kokoro-ele/a2ui-ink (@evanyu/a2ui-ink) |
Terminal / CLI (Ink) | β | β | β | GitHub Β· npm | |
yessGlory17/generative-mui (@yessglory/generative-mui-react) |
React + Material UI (Web) | β | β | β | GitHub Β· npm | |
ChaliceForAuri/a2ui-svelte (svelte-a2ui) |
Svelte 5 (Web) | β | β | β | GitHub Β· npm Β· Demo | |
Language-Lit/material3-expressive-a2ui (@language-lit/material3-expressive-a2ui) |
React + Material 3 Expressive (Web) | β | β | β | GitHub Β· npm Β· Demo & Docs | |
uny/a2ui-compose (dev.ynagai.a2ui:a2ui-material3) |
Compose Multiplatform (Android, iOS, desktop, macOS, web JS/wasmJs) | β | β | β | GitHub Β· Maven Central Β· API docs |
Notable Mentions
These projects are early-stage or experimental:
- xpert-ai/a2ui-react (
@xpert-ai/a2ui-react) β React renderer with ShadCN UI components (v0.0.1, published Jan 2026). - josh-english-2k18/a2ui-3d-renderer β Experimental Three.js/WebGL 3D renderer for A2UI (~2 stars).
- AINative-Studio/ai-kit-a2ui β React + ShadCN renderer for the AIKit framework (~2 stars).
Related Projects
These projects are not directly A2UI renderers but are closely related and do support A2UI:
| Project | Platform | Description | Links |
|---|---|---|---|
vercel-labs/json-render (@json-render/*) |
React, Vue, Svelte, Solid, React Native | Generative UI framework by Vercel β uses its own JSON schema (not A2UI protocol) with Zod-based component catalogs. Supports streaming, 36 pre-built shadcn/ui components, and cross-platform rendering. | GitHub Β· npm Β· Docs |
Agent-Side Libraries
These projects produce A2UI rather than rendering it.
| Project | Language | Description | Links |
|---|---|---|---|
Max-Health-Inc/prefab (@maxhealth.tech/prefab) |
TypeScript | Server-side component library that emits A2UI from a typed component tree, with a diagnostic for anything the Basic Catalog cannot express. Also serves A2UI over MCP as an a2ui:// resource or an embedded resource. |
GitHub Β· npm Β· Docs |
Ecosystem Utilities
- @a2ui/markdown-it β Enables markdown rendering in the Text widgets of all renderers.
Highlights
easyops-cn/a2ui-sdk (@a2ui-sdk/react) is the most fully-featured community React renderer, with 11 published versions, Radix UI primitives, Tailwind CSS styling, and a dedicated docs site. It was announced on the A2UI discussions. For the official A2UI React renderer, see @a2ui/react.
lmee/A2UI-Android fills an important gap β it's currently the only Jetpack Compose renderer, covering Android 5.0+ with 20+ components, data binding, and accessibility support.
sivamrudram-eng/a2ui-react-native is the only React Native renderer, enabling A2UI on iOS and Android via a single codebase.
BBC6BAE9/a2ui-swift is a Swift-based, spec-faithful native Apple renderer for
A2UI. It supports SwiftUI, UIKit, and AppKit through a shared A2UISwiftCore
layer, and runs across iOS, iPadOS, macOS, tvOS, visionOS, and watchOS. The
project supports A2UI v0.8, v0.9, and v0.9.1, with a focus on close alignment
with the official A2UI specification and Apple-native platform behavior. It
prioritizes spec-level interoperability and native platform expression over
pixel-identical rendering across different platforms. Default rendering uses
native Apple controls and platform-appropriate interaction patterns aligned with
the Human Interface Guidelines, while apps can still customize styling and
behavior through A2UI catalogs, themes, and custom components.
AGenUI/AGenUI is a cross-platform native renderer for A2UI v0.9, supporting iOS, Android, and HarmonyOS through a shared C++ core. It is designed for high performance, extensibility, and cross-platform consistency. AGenUI fully implements A2UI v0.9 and provides runtime APIs for extending UI components and function calls. It also introduces a custom Catalog that extends the Basic Catalog with Table, Carousel, Web, RichText, and a Styles property for richer appearance and layout control, while staying aligned with A2UIβs extensible Catalog model. See the component demo, the corresponding open-source A2UI Generation Skill, and learn more on GitHub.
lynx-family/lynx-stack (@lynx-js/genui/a2ui) provides the ReactLynx renderer for A2UI v0.9. It consumes validated server-to-client A2UI messages through a MessageStore, renders approved ReactLynx components from caller-supplied catalogs, and forwards generated UI actions through onAction. The published @lynx-js/genui package exposes the A2UI renderer via the a2ui subpath, while the GenUI CLI supports build-time catalog artifact and A2UI system prompt generation.
yessGlory17/generative-mui (@yessglory/generative-mui-react) is a Material UI renderer for A2UI (v0.9.1). It maps all 18 Basic Catalog components one-to-one onto MUI and renders them inside the host app's existing `` β the surface has no theme of its own, so it inherits the host palette/typography and re-skins top-to-bottom when the theme changes. It splits into a framework-neutral core (@yessglory/generative-mui-core β Zod schemas, JSONL parser, JSON Pointer, a deterministic subscribable SurfaceStore, and provider-agnostic agent tool schemas) and the React/MUI adapter, with the one-way react β core dependency enforced at lint time. Beyond the Basic Catalog it ships an opt-in Extended Catalog (charts via @mui/x-charts, Table, and more), two-way data binding, checks-driven validation, streaming resilience (incremental skeletons, append-only string deltas, cycle/depth and per-node error boundaries), and security guards (unregistered types never execute; sx/style/className from the agent are stripped; a ReDoS length cap on local regex).
ChaliceForAuri/a2ui-svelte (svelte-a2ui) is a Svelte 5 renderer built natively on runes, targeting the v1.0 Candidate spec (component property names shared with v0.9). It covers the full Basic Catalog β all 18 components, the complete 59-name Icon enum plus the {svgPath} variant, all 14 built-in functions, checks validation, and collection templates β and replays the specification's own contact-form fixture in its test suite. The protocol core is a pure reducer with structural sharing, held in $state.raw so unchanged components skip re-rendering entirely; the threat model assumes a hostile agent (catalog allowlist, on*/function stripping, prototype-pollution refusal, escape-then-markup for text, bounded recursion, callableFrom enforcement). Transports include HTTP JSONL/SSE, the A2A extension binding (application/a2ui+json DataParts, renderer metadata lifting), and AG-UI activities (including the middleware's a2ui-surface type), with a2uiRendererCapabilities advertised on every outbound message. Styling is entirely host-owned CSS custom properties. The project is Apache-2.0 licensed, and a hosted demo streams the booking-form scenario live.
Language-Lit/material3-expressive-a2ui (@language-lit/material3-expressive-a2ui) is a Material 3 Expressive renderer for A2UI v0.9.1, built on Google's @a2ui/web_core message processor. It maps all 18 Basic Catalog components one-to-one onto the Material 3 Expressive React design system, so agent-generated surfaces share one theme, one type scale, and one motion system with the rest of the host app. It ships a useA2ui hook and an A2uiSurface component, two-way data binding, checks-driven validation, streaming placeholders for children named before they arrive, actions with resolved context, and an extensible catalog. The same catalog also plugs into the official @a2ui/react surface. The package has zero runtime dependencies beyond its peers (React, the design system, and web_core), styles only through design tokens, and is MIT licensed. A scripted live demo streams four scenarios in the browser with no backend.
uny/a2ui-compose (dev.ynagai.a2ui:a2ui-material3) is a Compose Multiplatform renderer that draws Android, iOS, desktop (JVM), macOS and web (Kotlin/JS and Kotlin/Wasm) from a single commonMain. It targets the v1.0 specification only and carries no v0.8/v0.9 compatibility layer, so it is listed under v1.0 only β see the protocol version note for why. The Material 3 module covers all 18 Basic Catalog components (every component the specification's 43 examples name), the built-in functions, checks validation and collection templates; a2ui-core is a Compose-free module with the protocol types, JSON Pointer binding and function evaluation, and a2ui-compose sits between the two with no design system attached, for hosts that bring their own widgets. Markdown in Text is a parser-free subset by default, with lists, tables and code fences behind an optional a2ui-material3-markdown module. The renderer fetches nothing itself β images go through a host-provided loader, and links are reduced to their labels so nothing opens a URL past openUrl. Apache-2.0, published to Maven Central, with a gallery app that replays the specification's examples on desktop, wasmJs and native macOS.
Submitting a Renderer
If you have built an A2UI renderer, submit it to be listed here.
How to submit
To submit a renderer, follow these steps:
- Fork the a2ui-project/a2ui repository
- Edit this file (
docs/ecosystem/renderers.md) β add a row to the Community Renderers table with your renderer's name, platform, npm package (if any), version support, and a link to the source - Open a PR against
a2ui-project/a2uiwith a short description of your renderer - Post in GitHub Discussions β let the community know what you built! A short demo video goes a long way.
Need inspiration? Browse the community samples in the repo β these cover Angular, Lit, and ADK-based agents and are a good starting point.
What makes a good community renderer?
A listing is more likely to be accepted and used if it meets the following criteria:
- Has published source code (open-source preferred, MIT or Apache 2.0).
- Clearly states which A2UI spec version(s) it supports (v0.8, v0.9, v1.0).
- Covers the basic components from the other A2UI renderers: text, buttons, inputs, basic layout components, etc.
- Includes a README with install instructions and a minimal usage example.
- Is actively maintained β flag it as archived if you're no longer supporting it.
Community renderers don't need to be production-ready to be listed β experimental and early-stage projects are welcome in the Notable Mentions section.
Public Guides A2ui With Any Agent Framework (`docs/public/guides/a2ui-with-any-agent-framework.md`)
Use A2UI with Any Agent Framework & Harness
A2UI is a declarative UI format. AG-UI is the transport
that carries A2UI messages between an agent and an app. Use this guide to add
A2UI to an AG-UI app or harness backed by ADK, LangGraph, Mastra, Strands,
CrewAI, Google Chat, Slack, or any other agent framework or service that
supports AG-UI.
The examples below use AG-UI-compatible runtime tooling so you can focus on
the A2UI surface: enabling the renderer, giving your agent a catalog, and
streaming UI updates back to the user. For protocol-level setup and concepts,
see the AG-UI docs.
Agent skills
If you are using a coding agent to wire this up, load the
AG-UI ag-ui-a2ui-integration skill
before it modifies your app. It covers AG-UI framework adapters, supportedcreate-ag-ui-app flags, transport setup, A2UI runtime and renderer wiring,
and end-to-end verification for AG-UI + A2UI apps.
If your app uses CopilotKit for A2UI rendering, also load the
CopilotKit copilotkit skill
for CopilotKit v2 runtime, provider, theme, and catalog conventions.
1. Set up AG-UI
Start from the agent framework you already use, then add an AG-UI runtime
connection between the agent and your app. The runtime streams agent events,
including A2UI messages, to the client surface.
Use the AG-UI CLI to scaffold an AG-UI app with the client and agent framework
you want:
npx create-ag-ui-app@latestYou can also start directly from supported framework templates:
npx create-ag-ui-app@latest --adk
npx create-ag-ui-app@latest --langgraph-py
npx create-ag-ui-app@latest --langgraph-jsStrands has no scaffold flag yet β wrap an existing Strands agent (see the
Strands panels below).
The important part is the transport contract: your app receives AG-UI events
and routes A2UI payloads to an A2UI renderer. Some scaffold paths use
CopilotKit's A2UI runtime
with Next.js under the hood, but the setup surface stays AG-UI-first.
2. Set up your agent or harness
The A2UI steps are the same across frameworks: connect your agent to AG-UI,
enable A2UI payloads, and render those payloads in the app. Start with the
framework or harness you already use. The snippets below come from the
corresponding AG-UI integrations and show the framework-native agent shape
that AG-UI wraps.
=== "ADK"
Use ADK when your agent already runs on Google's Agent Development Kit. The
AG-UI ADK middleware exposes the agent as an AG-UI event stream:
```python
from fastapi import FastAPI
from ag_ui_adk import ADKAgent, AGUIToolset, add_adk_fastapi_endpoint
from google.adk.agents import Agent
my_agent = Agent(
name="assistant",
instruction="You are a helpful assistant.",
tools=[
AGUIToolset(), # Adds tools provided by the AG-UI client.
],
)
agent = ADKAgent(
adk_agent=my_agent,
app_name="my_app",
user_id="user123",
)
app = FastAPI()
add_adk_fastapi_endpoint(app, agent, path="/chat")
```
See the
[AG-UI ADK middleware](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/adk-middleware/python).=== "LangGraph (Python)"
Use LangGraph when your agent workflow is a graph of stateful nodes. Start from
your normal LangGraph agent β A2UI needs no extra tool wiring on the graph:
```python
from copilotkit import CopilotKitMiddleware
from langchain.agents import create_agent
from langchain_google_genai import ChatGoogleGenerativeAI
gemini = ChatGoogleGenerativeAI(
model="gemini-2.5-pro",
thinking_budget=1024,
)
# A plain LangGraph agent β no A2UI tool wiring on the graph. The CopilotKit
# runtime forwards your frontend catalog and injects the `generate_a2ui` tool;
# include CopilotKitMiddleware to get A2UI capability.
graph = create_agent(
model=gemini,
tools=[],
middleware=[CopilotKitMiddleware()],
system_prompt="You are a helpful assistant.",
)
```
LangGraph's A2UI tool runs in the CopilotKit middleware layer, so include
`CopilotKitMiddleware` to get A2UI capability. The CopilotKit runtime forwards
your catalog and injects `generate_a2ui` automatically. The example uses Gemini
via LangChain's Google GenAI integration.
See the
[AG-UI LangGraph integration](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/langgraph/python)
and the
[ChatGoogleGenerativeAI integration](https://docs.langchain.com/oss/python/integrations/chat/google_generative_ai).=== "LangGraph (FastAPI)"
Use the FastAPI variant when you serve the same LangGraph graph behind a FastAPI
app. The agent shape is identical β export the same `graph` and serve it through
the AG-UI LangGraph endpoint:
```python
from copilotkit import CopilotKitMiddleware
from langchain.agents import create_agent
from langchain_google_genai import ChatGoogleGenerativeAI
gemini = ChatGoogleGenerativeAI(
model="gemini-2.5-pro",
thinking_budget=1024,
)
graph = create_agent(
model=gemini,
tools=[],
middleware=[CopilotKitMiddleware()],
system_prompt="You are a helpful assistant.",
)
```
See the
[AG-UI LangGraph integration](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/langgraph/python).=== "LangGraph (TypeScript)"
Use the TypeScript variant when your LangGraph agent is written in TypeScript.
The shape mirrors the Python agent β a plain graph plus the CopilotKit
middleware:
```ts
import { createAgent } from "langchain";
import { ChatOpenAI } from "@langchain/openai";
import { copilotkitMiddleware } from "@copilotkit/sdk-js/langgraph";
export const graph = createAgent({
model: new ChatOpenAI({ model: "gpt-4o" }),
tools: [],
middleware: [copilotkitMiddleware],
systemPrompt: "You are a helpful assistant.",
});
```
See the
[AG-UI LangGraph TypeScript integration](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/langgraph/typescript).=== "Strands (Python)"
Use Strands when your agent orchestration is built on AWS Strands. Wrap a plain
Strands agent with the AG-UI Strands adapter:
```python
from strands import Agent
from ag_ui_strands import StrandsAgent
strands_agent = Agent(
system_prompt="You are a helpful assistant.",
)
agent = StrandsAgent(
agent=strands_agent,
name="my-agent",
description="A Strands agent exposed via AG-UI",
)
```
See the
[AG-UI AWS Strands integration](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/aws-strands/python).=== "Strands (TypeScript)"
Use the TypeScript variant when your Strands agent is written in TypeScript. The
AG-UI Strands adapter wraps the Strands agent for AG-UI clients:
```ts
import { Agent } from "@strands-agents/sdk";
import { StrandsAgent } from "@ag-ui/aws-strands";
import { createStrandsApp } from "@ag-ui/aws-strands/server";
const strandsAgent = new Agent({
systemPrompt: "You are a helpful assistant.",
tools: [],
});
const aguiAgent = new StrandsAgent({
agent: strandsAgent,
name: "MyAgent",
description: "A Strands agent exposed via AG-UI",
});
const app = await createStrandsApp(aguiAgent, { path: "/invocations" });
app.listen(8000);
```
See the
[AG-UI AWS Strands integration](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/aws-strands/typescript).=== "Slack"
Use Slack when the user experience lives in a Slack app. Route the Slack thread
into the same AG-UI agent endpoint. The same AG-UI event stream can feed a
Slack harness and render A2UI through the surface's client bridge.
<video class="agui-demo-video" controls playsinline preload="metadata">
Your browser does not support the video tag.
</video>
CopilotKit's Slack adapter already implements this pattern:
```ts
import { createBot } from "@copilotkit/bot";
import {
slack,
SanitizingHttpAgent,
defaultSlackTools,
defaultSlackContext,
} from "@copilotkit/bot-slack";
const bot = createBot({
adapters: [
slack({
botToken: process.env.SLACK_BOT_TOKEN!,
appToken: process.env.SLACK_APP_TOKEN!,
}),
],
agent: (threadId) => {
const agent = new SanitizingHttpAgent({
url: process.env.AGENT_URL!,
});
agent.threadId = threadId;
return agent;
},
tools: [...defaultSlackTools],
context: [...defaultSlackContext],
});
bot.onMention(async ({ thread }) => {
await thread.runAgent();
});
await bot.start();
```These snippets establish the AG-UI server connection. Slack uses the same
AG-UI/A2UI contract through its own harness and client bridge. The next
sections turn on A2UI rendering, catalogs, and component definitions inside the
app surface.
3. Enable A2UI
Start from the developer experience you want: define the catalog definitions the
agent can see, map each definition to a renderer, create the catalog, and pass
that catalog into CopilotKit. The frontend catalog config is the target A2UI
activation surface.
import {CopilotKit, CopilotChat} from '@copilotkit/react-core/v2';
import {
createCatalog,
type CatalogDefinitions,
type CatalogRenderers,
} from '@copilotkit/a2ui-renderer';
import {z} from 'zod';
// catalog definitions β describe the building block components to the agent
export const catalogDefinitions = {
Card: {
description: 'A titled card container.',
props: z.object({title: z.string(), subtitle: z.string().optional()}),
},
PrimaryButton: {
description: 'A styled primary button.',
props: z.object({label: z.string(), action: z.any().optional()}),
},
} satisfies CatalogDefinitions;
// catalog renderers β how each primitive renders in the DOM (React, in this example)
export const catalogRenderers = {
Card: MyCard,
PrimaryButton: MyPrimaryButton,
} satisfies CatalogRenderers<typeof catalogDefinitions>;
// definitions + renderers together define a catalog declaration
const catalog = createCatalog(catalogDefinitions, catalogRenderers, {
catalogId: 'my-catalog',
includeBasicCatalog: true,
});
<CopilotKit runtimeUrl="/api/copilotkit" a2ui={{catalog}}>
<CopilotChat />
</CopilotKit>;Passing a catalog to the provider auto-enables A2UI and injects thegenerate_a2ui tool, so your agent can produce surfaces with no extra runtime
config (CopilotKit β₯ 1.61.2). You can opt out, or opt in manually without a
catalog, by configuring the runtime directly:
import {CopilotRuntime} from '@copilotkit/runtime';
const runtime = new CopilotRuntime({
agents: {default: myAgent},
a2ui: {injectA2UITool: true},
});Scope to specific agents with a2ui: { injectA2UITool: true, agents: ["my-agent"] }.
For fixed-schema flows where your agent already returns a2ui_operations,a2ui: true or a2ui: {} is enough.
Custom components (BYOC)
A2UI ships with a built-in catalog (Text, Image, Card, β¦) that gets you a
working surface immediately. The expanded BYOC flow below shows the same
catalog pattern split across files for a real app:
- Definitions: Zod schemas plus a natural-language description. This
is what the agent sees in its system prompt. Note that for client-side functions, the client determines the function's execution boundary (such as clientOnly status) at runtime by reading its configuration from the active catalog definition. - Renderers: Typed React components, one per definition. This is
what the user sees. - Registration: Pass the catalog through the provider so the A2UI
renderer knows how to draw your components.
1. Define component schemas
Create platform-agnostic definitions with Zod. The description field
gets injected into the agent's prompt so the LLM knows when to reach for
each component; the schema validates the props the agent sends.
import {z} from 'zod';
export const myDefinitions = {
StatusBadge: {
description: 'A colored status badge.',
props: z.object({
text: z.string(),
variant: z.enum(['success', 'warning', 'error']).optional(),
}),
},
Metric: {
description: 'A key metric with label and value.',
props: z.object({
label: z.string(),
value: z.string(),
trend: z.enum(['up', 'down']).optional(),
}),
},
};
export type MyDefinitions = typeof myDefinitions;2. Create React renderers
Map each definition to a React component. createCatalog is generic over
the definitions type, so the props your renderer receives are type-checked
against the Zod schema, so a typo in props.text is a compile error.
'use client';
import {createCatalog, type CatalogRenderers} from '@copilotkit/a2ui-renderer';
import {myDefinitions, type MyDefinitions} from './definitions';
const myRenderers: CatalogRenderers<MyDefinitions> = {
StatusBadge: ({props}) => {
const colors = {
success: {bg: '#dcfce7', text: '#166534'},
warning: {bg: '#fef3c7', text: '#92400e'},
error: {bg: '#fee2e2', text: '#991b1b'},
};
const c = colors[props.variant ?? 'success'];
return (
<span
style={{
padding: '2px 8px',
borderRadius: 9999,
fontSize: '0.75rem',
background: c.bg,
color: c.text,
}}
>
{props.text}
</span>
);
},
Metric: ({props}) => (
<div>
<div style={{fontSize: '0.75rem', color: '#6b7280'}}>{props.label}</div>
<div style={{fontSize: '1.5rem', fontWeight: 700}}>
{props.value} {props.trend === 'up' ? 'β' : props.trend === 'down' ? 'β' : ''}
</div>
</div>
),
};
export const myCatalog = createCatalog(myDefinitions, myRenderers, {
catalogId: 'my-app-catalog',
includeBasicCatalog: true, // merges with built-in components
});catalogId is the stable handle the agent uses to target this catalog;includeBasicCatalog: true keeps the built-in components available
alongside your own (omit it to render only your components).
3. Pass the catalog to CopilotKit
'use client';
import {CopilotKitProvider} from '@copilotkit/react-core/v2';
import '@copilotkit/react-core/v2/styles.css';
import {myCatalog} from '@/lib/a2ui/renderers';
export default function Layout({children}: {children: React.ReactNode}) {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit" a2ui={{catalog: myCatalog}}>
{children}
</CopilotKitProvider>
);
}Agents will now see your custom components alongside the built-ins and
can use them in any A2UI surface they emit.
For the full BYOC reference (multiple catalogs, theming hooks, advanced
patterns), see CopilotKit's
Custom Components (BYOC) section.
4. Advanced usage
For the full A2UI integration surface (custom catalogs, fine-grained control,
advanced patterns), see CopilotKit's
A2UI docs.
What's next
- A2UI Composer: Build widgets visually.
- Concepts βΊ Transports: How A2UI maps onto AG-UI.
- v0.9 specification: The underlying protocol.
Public Guides A2ui Over Mcp (`docs/public/guides/a2ui_over_mcp.md`)
A2UI over Model Context Protocol (MCP)
This guide shows you how to serve rich, interactive A2UI interfaces from an MCP server using Tools and Embedded Resources. By the end, you'll have a working MCP server that returns A2UI components to any MCP-compatible client.
Prerequisites
Ensure you have the following installed before you begin:
- Python (version 3.10 or later).
- uv for fast Python package management.
- Node.js (version 18 or later) for the MCP Inspector.
Quick Start: Run the Sample
Before diving into the protocol details, let's get a working example running. The A2UI repository includes a ready-to-go MCP recipe demo.
# Clone the repo (if you haven't already)
git clone https://github.com/a2ui-project/a2ui.git
cd a2ui/samples/community/mcp/a2ui-over-mcp-recipe
# Start the MCP server (SSE transport on port 8000)
uv run .Option A: Interacting via the MCP Inspector
In a separate terminal, launch the MCP Inspector to interact with the server:
npx @modelcontextprotocol/inspector@latest --web --transport sse --server-url http://localhost:8000/sseOpen http://localhost:6274:
- Click List Resources β you will see
a2ui://recipe-formanda2ui://recipe-card. - Read a resource β the content contains the static A2UI presentation template (
createSurfaceandupdateComponents) with data bindings. - Click List Tools β you will see
get_recipe_form_a2uiandget_recipe_a2ui, both carrying_meta.uilinks to their respective presentation template resource. - Run
get_recipe_form_a2uiβ the tool returns the initial form selection state wrapped in anupdateDataModelmessage. - Run
get_recipe_a2uiwith custom parameters β the tool returns dynamic recipe details wrapped in anupdateDataModelmessage.
[!NOTE]
The sample uses a local path reference to the A2UI Agent SDK. For your own projects, install from PyPI:bashpip install a2ui-agent-sdk
Option B: Running the Recipe Client Web App
To run the interactive web client:
[!NOTE]
Running built-in sample applications within the A2UI repository uses Yarn workspaces (yarn install/yarn dev). Outside this repository, you can use any package manager (npm, pnpm, yarn).
- In a new terminal window, navigate to the client directory:bash
cd client - Install dependencies:bash
yarn install - Start the Vite development server:bash
yarn dev - Open your browser to
http://localhost:5173.
When the application loads, the client connects to the MCP server via SSE and executes get_recipe_form_a2ui. It reads _meta.ui to fetch and cache the a2ui://recipe-form presentation template, then applies the returned updateDataModel to populate default choices (Grilled, Chicken). Picking options and clicking "Get Recipe" executes get_recipe_a2ui, fetching a2ui://recipe-card and dynamically rendering the recipe details in the right column.
See all samples at samples/community/mcp/.
Decoupled Architecture: Separating Presentation from Data
A2UI over MCP separates user interfaces into two layers:
- Static Presentation Templates via MCP Resources (
resources/read):
Layouts containing component trees (createSurfaceandupdateComponents) with data bindings (such as/title,/cookTime,/image) are served as MCP resources under custom URIs (e.g.,a2ui://recipe-form,a2ui://recipe-card) with MIME typeapplication/a2ui+json. Because templates contain no hardcoded data values, the client can fetch and cache them locally. - Dynamic Data Updates via MCP Tools (
tools/call):
When a tool executes, the server returns only the dynamic values needed by the template, packaged as an A2UIupdateDataModelmessage. - Tool UI Metadata (
_meta.ui):
The tool links to its presentation template by including a_meta.uiobject in its tool definition and in itsCallToolResult:json"_meta": { "ui": { "resourceUri": "a2ui://recipe-card", "mimeType": "application/a2ui+json" } } - Client-Side Resolution & Hydration:
The client host inspects_meta.ui.resourceUri, checks its local template cache (or fetches the resource from the server on first load), initializes the surface layout, and applies the dynamicupdateDataModelfrom the tool response.
[!IMPORTANT]
MIME Type Uniformity
Both static template resources and dynamic tool payloads use theapplication/a2ui+jsonMIME type. In tool responses, data model updates are returned inside anEmbeddedResourcealongside a fallbackTextContent. This identification allows client applications to route payloads directly to A2UI processors.
Delivery Flow
1. Tool Invocation
Client β tools/call (e.g. get_recipe_a2ui) β MCP Server
β
Compute dynamic values
β
Client β CallToolResult (updateDataModel) β MCP Server
+ _meta.ui: { resourceUri: "a2ui://recipe-card" }
2. Template Resolution (Cached After First Fetch)
If "a2ui://recipe-card" is not in client cache:
Client β resources/read ("a2ui://recipe-card") β MCP Server
Client β Template (createSurface, updateComponents) β MCP Server
3. Surface Hydration
Client applies updateDataModel from tool response to the surface.
A2UI Renderer updates display.1. Defining Presentation Templates as MCP Resources
Expose static layout templates via resources/list and resources/read:
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="a2ui://recipe-form",
name="Recipe Form",
mimeType="application/a2ui+json",
description="Static form allowing users to pick cuisine and protein.",
),
types.Resource(
uri="a2ui://recipe-card",
name="Recipe Card",
mimeType="application/a2ui+json",
description="Static recipe card layout template.",
),
]
@app.read_resource()
async def read_resource(uri: str) -> list[ReadResourceContents]:
if str(uri) == "a2ui://recipe-form":
return [
ReadResourceContents(
content=json.dumps(recipe_form_json),
mime_type="application/a2ui+json",
)
]
if str(uri) == "a2ui://recipe-card":
return [
ReadResourceContents(
content=json.dumps(recipe_a2ui_json),
mime_type="application/a2ui+json",
)
]
raise ValueError(f"Unknown resource: {uri}")2. Declaring Tool UI Metadata
Declare the presentation resource URI on the tool definition:
types.Tool(
name="get_recipe_a2ui",
title="Get Recipe A2UI",
description="Returns recipe data and links to the recipe-card template.",
inputSchema={
"type": "object",
"properties": {
"cookingStyle": {
"type": "array",
"items": {"type": "string"},
"description": "Selected cooking styles",
},
"protein": {
"type": "array",
"items": {"type": "string"},
"description": "Selected proteins",
},
},
"additionalProperties": True,
},
_meta={
"ui": {
"resourceUri": "a2ui://recipe-card",
"mimeType": "application/a2ui+json",
}
},
)3. Returning Dynamic Data via Tool Execution
In the tool call handler, return the dynamic state as an updateDataModel message with _meta.ui:
/* Detailed source-code truncated for AI context efficiency. */Catalog Negotiation
Before a server can send A2UI to a client, they must establish which catalogs are available. Depending on your architecture, this can happen in one of two ways.
Option A: During MCP Initialization (Recommended)
MCP is a stateful session protocol, so the most efficient approach is to declare capabilities once during connection setup. The client declares its A2UI support under capabilities:
{
"jsonrpc": "2.0",
"method": "initialize",
"id": "init-123",
"params": {
"protocolVersion": "2025-11-25",
"clientInfo": {
"name": "a2ui-enabled-client",
"version": "1.0.0"
},
"capabilities": {
"a2ui": {
"clientCapabilities": {
"v0.9": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_9/basic_catalog.json"
]
}
}
}
}
}
}The server stores this state for the duration of the session.
Option B: Per-Message Metadata (For Stateless Servers)
If your server must remain stateless, the client can pass A2UI capabilities in the _meta field of every tool call:
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": "id-123",
"params": {
"name": "generate_report",
"arguments": {"date": "2026-03-01"},
"_meta": {
"a2ui": {
"clientCapabilities": {
"v0.9": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_9/basic_catalog.json"
],
"inlineCatalogs": []
}
}
}
}
}
}Handling User Actions
Interactive components like Button can trigger actions that are sent back to the server as MCP tool calls.
1. Define a Button with an Action
In your A2UI JSON, add an action to a component:
{
"id": "confirm-button",
"component": {
"Button": {
"child": "confirm-button-text",
"action": {
"event": {
"name": "confirm_booking",
"context": {
"start": "/dates/start",
"end": "/dates/end"
}
}
}
}
}
}2. Client Sends the Action as a Tool Call
When the user clicks the button, the client resolves data bindings (like /dates/start) against the surface state and sends a tool call with the required action fields:
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": "id-456",
"params": {
"name": "a2ui_action",
"arguments": {
"name": "confirm_booking",
"surfaceId": "booking-surface",
"sourceComponentId": "confirm-button",
"timestamp": "2026-03-20T12:00:00Z",
"context": {
"start": "2026-03-20",
"end": "2026-03-25"
}
}
}
}3. Handle the Action on the Server
@app.tool()
async def a2ui_action(
name: str,
surfaceId: str,
sourceComponentId: str,
timestamp: str,
context: dict[str, Any],
) -> types.CallToolResult:
"""Handle A2UI user actions."""
if name == "confirm_booking":
# Process the booking, then return confirmation UI
return types.CallToolResult(content=[
types.TextContent(
type="text",
text=f"Booking confirmed for {surfaceId}: {context['start']} to {context['end']}"
)
])
raise ValueError(f"Unknown action: {name}")[!NOTE]
All five action fields (name,surfaceId,sourceComponentId,timestamp, andcontext) are required by the A2UI specification. Declaring all fields in the tool parameters prevents MCP SDKs from strippingsurfaceIdor other fields, losing the originating surface context.
Error Handling
Clients can report A2UI rendering and validation errors back to the server via a tool call:
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": "id-789",
"params": {
"name": "a2ui_error",
"arguments": {
"code": "VALIDATION_FAILED",
"surfaceId": "booking-surface",
"path": "/components/0/text",
"message": "Failed to parse A2UI payload."
}
}
}Handle it on the server:
@app.tool()
async def a2ui_error(
code: str,
surfaceId: str,
message: str,
path: str | None = None,
) -> types.CallToolResult:
"""Handle A2UI client errors."""
# Log the error, retry, or send a fallback UI
return types.CallToolResult(content=[
types.TextContent(
type="text",
text=f"Acknowledged error {code} on surface {surfaceId}: {message}"
)
])Verbalization and Visibility Control
Control whether the LLM can "read" A2UI payloads in subsequent turns using MCP Resource Annotations:
a2ui_resource = types.EmbeddedResource(
type="resource",
resource=types.TextResourceContents(
uri="a2ui://training-plan-page",
mimeType="application/a2ui+json",
text=json.dumps(a2ui_payload)
),
# Show the UI to the user, but hide the raw JSON from the LLM
annotations=types.Annotations(audience=["user"])
)| Audience | Behavior |
|---|---|
| (empty) | Visible to both user and LLM |
["user"] |
Rendered for the user; hidden from LLM context |
["assistant"] |
Available to LLM for follow-up reasoning; not rendered |
Using the A2UI Agent SDK
For production use, the A2UI Agent SDK handles schema management, validation, and prompt generation for you:
pip install a2ui-agent-sdkfrom a2ui.core.basic_catalog import BasicCatalog
from a2ui.core.schema import ProtocolVersion
from a2ui.inference_formats.direct_json import DirectJsonFormat
from a2ui.utils import validate_payload
# Initialize the inference format with the basic catalog
inference_format = DirectJsonFormat([BasicCatalog(ProtocolVersion.V0_9)])
# Validate A2UI output before sending. This raises A2uiValidationError if a
# renderer holding the catalog would reject the payload.
selected_catalog = inference_format.catalogs[0]
validate_payload([selected_catalog], a2ui_payload)See the full Agent Development Guide for details on schema management, dynamic catalogs, and streaming.
Next Steps
- A2UI Specification β full protocol reference
- Component Gallery β browse available components
- MCP Apps in A2UI Surface β embed HTML-based MCP apps inside A2UI
- Client Setup β build a renderer that displays A2UI
Public Guides Agent Development (`docs/public/guides/agent-development.md`)
Agent Development Guide
Build AI agents that generate A2UI interfaces. This guide covers generating and streaming UI messages from LLMs.
Quick Overview
Building an A2UI agent:
- Understand user intent β Decide what UI to show
- Generate A2UI JSON β Use LLM structured output or prompts
- Validate & stream β Check schema, send to client
- Handle actions β Respond to user interactions
Start with a simple agent
This guide uses the ADK to build a simple agent, starting with text and upgrading it to A2UI.
See step-by-step instructions at the ADK quickstart.
pip install google-adk
adk create my_agentTIP: If you are using
uvand working within the sample directories (or a project that already depends ongoogle-adk), you can useuv run adkinstead of installing it globally:bashuv run adk create my_agent
Then edit the my_agent/agent.py file with a very simple agent for restaurant recommendations.
/* Detailed source-code truncated for AI context efficiency. */Don't forget to set the GOOGLE_API_KEY environment variable to run this example.
echo 'GOOGLE_API_KEY="YOUR_API_KEY"' > .envYou can test out this agent with the ADK web interface:
adk webSelect my_agent from the list, and ask questions about restaurants in New York. You should see a list of restaurants in the UI as plain text.
Generating A2UI Messages
Getting the LLM to generate A2UI messages requires some prompt engineering. The SDK provides the DirectJsonFormat inference format to help you generate a system prompt that includes the A2UI schema and examples from your component catalog.
First, make sure you have a2ui-agent-sdk installed (it is included in the samples). The agent SDK does not depend on an agent framework. The A2A and ADK integrations are separate packages: install a2ui-a2a to serve A2UI over A2A, and a2ui-adk[a2a] to use the ADK toolset and converters.
In your agent file (e.g., agent.py), import the necessary classes:
from a2ui.core.basic_catalog import BasicCatalog
from a2ui.core.schema import ProtocolVersion
from a2ui.inference_formats.direct_json import DirectJsonFormatThen, you can use DirectJsonFormat to generate the system prompt. This ensures that the schema and examples are correctly formatted and up to date.
/* Detailed source-code truncated for AI context efficiency. */Understanding the Output
Your agent will no longer strictly output text. Instead, it will output text and a JSON list of A2UI messages.
The A2UI_SCHEMA that we imported is a standard JSON schema that defines valid operations like:
render(displaying a UI)update(changing data in an existing UI)
Because the output is structured JSON, you may parse and validate it before sending it to the client.
# 1. Parse the JSON
# Warning: Parsing the output as JSON is a fragile implementation useful for documentation.
# LLMs often put Markdown fences around JSON output, and can make other mistakes.
# Rely on frameworks to parse the JSON for you.
parsed_json_data = json.loads(json_string_cleaned)
# 2. Validate against A2UI_SCHEMA
# This ensures the LLM generated valid A2UI commands
jsonschema.validate(
instance=parsed_json_data, schema=self.a2ui_schema_object
)By validating the output against A2UI_SCHEMA, you ensure that your client never receives malformed UI instructions.
TODO: Continue this guide with examples of how to parse, validate, and send the output to the client renderer without the A2A extension.
Public Guides Authoring Components (`docs/public/guides/authoring-components.md`)
Authoring Custom Components
!!! tip "Authoring components for multiple frameworks?"
See [Authoring Universal Components](authoring-universal-components.md) to learn how to write a single W3C Custom Element that works directly across Lit, React, and Angular renderers.Learn how to define, implement, and register custom components in A2UI using the rizzcharts sample as an example. This guide focuses on authoring a component around your Angular code.
Overview
Authoring a new component involves four main steps:
- Define the Catalog Schema: Specify the component's properties and types in a JSON Schema.
- Define the Component (Client): Implement the UI using your framework (e.g., Angular).
- Register with the Renderer (Client): Add the component to your client-side catalog.
- Invoke from the Agent: Instruct the agent to use the component via
send_a2ui_json_to_client.
1. Defining the Catalog Schema
The catalog schema defines the API of your catalog. It lists available components and their properties, which the agent uses to construct UI payloads.
This schema acts as a contract between the client and the server (agent). Both must agree on this schema for rendering to work. The client advertises what catalogs it supports, and the server selects a compatible one. For details on how this handshake works, see A2UI Catalog Negotiation.
In the rizzcharts example, the catalog schema is defined in rizzcharts_catalog_definition.json.
Here is the schema for the Chart component:
/* Detailed source-code truncated for AI context efficiency. */2. Implementing the Component (Client)
Implement your component using your client-side framework. For Angular, your component should extend CatalogComponent provided by @a2ui/angular/v0_9.
In the rizzcharts example, the Chart component is defined in chart.ts.
First, define the component API in TypeScript. This should match the JSON Schema defined in Step 1.
// api.ts
import {ComponentApi} from '@a2ui/web_core/v0_9';
import {z} from 'zod';
export const ChartApi = {
name: 'Chart',
schema: z.object({
type: z.enum(['doughnut', 'pie']),
title: z.string().optional(),
chartData: z.array(
z.object({
label: z.string(),
value: z.number(),
drillDown: z.array(
z.object({
label: z.string(),
value: z.number(),
})
).optional(),
})
),
}).strict(),
} satisfies ComponentApi;Now implement the Angular component:
import {CatalogComponent} from '@a2ui/angular/v0_9';
import {Component, computed} from '@angular/core';
import {BaseChartDirective} from 'ng2-charts';
import {ChartApi} from './api';
@Component({
selector: 'a2ui-chart',
imports: [BaseChartDirective],
template: `
<div>
<h2>{{ title() }}</h2>
<canvas baseChart [data]="chartData()" [type]="chartType()"></canvas>
</div>
`,
})
export class Chart extends CatalogComponent<typeof ChartApi> {
protected readonly chartType = computed(() => this.props()['type']?.value() || 'pie');
protected readonly title = computed(() => this.props()['title']?.value() || '');
protected readonly chartData = computed(() => {
const rawData = this.props()['chartData']?.value() || [];
return {
labels: rawData.map(item => item.label),
datasets: [
{
data: rawData.map(item => item.value),
},
],
};
});
}Keep these key points in mind when implementing components:
- Extend
CatalogComponent: This gives you access to the type-safepropssignal input. - Use
props()Signal: Access resolved properties reactively viathis.props()['propertyName']?.value(). The framework automatically handles resolving data bindings and expressions.
3. Registering with the Renderer (Client)
Once the component is implemented, register it in your client catalog. This maps the component name (used by agents) to the implementation class.
You use the AngularCatalog class to define your catalog.
import {AngularCatalog, BASIC_COMPONENTS, BASIC_FUNCTIONS} from '@a2ui/angular/v0_9';
import {Chart} from './chart';
import {ChartApi} from './api';
const customChartComponent = {
...ChartApi,
component: Chart
};
export const RIZZ_CHARTS_CATALOG = new AngularCatalog(
'https://github.com/.../rizzcharts_catalog_definition.json',
[...BASIC_COMPONENTS, customChartComponent],
BASIC_FUNCTIONS
);Key points for registration:
- Eager Registration: Component classes are registered directly in the catalog definition.
4. Invoking from the Agent
To use the custom component, you initialize the agent with tools from the A2UI SDK that understand your catalog. The SDK handles resolving the catalog and providing examples to the model.
Here is how the flow wires up:
4.1 Session Preparation (Executor)
The execution layer (e.g., RizzchartsAgentExecutor) intercepts the incoming message to detect if A2UI is enabled and what catalogs the client supports. It resolves the catalog and saves it to the session state.
# In agent_executor.py
from a2ui.inference_formats.direct_json import DirectJsonFormat
from a2ui.utils import resolve_catalogs
use_ui = try_activate_a2ui_extension(context)
if use_ui:
# Resolve the catalogs based on client capabilities
catalogs = resolve_catalogs(
self.catalog_configs, capabilities, accepts_inline_catalogs=True
)
inference_format = DirectJsonFormat(catalogs, examples_path=self.examples_path)
examples = inference_format.prompt_generator.generate_examples(validate=True)
# Save to session (Event contains state_delta)
await runner.session_service.append_event(
session,
Event(
actions=EventActions(
state_delta={
_A2UI_ENABLED_KEY: True,
_A2UI_CATALOGS_KEY: catalogs,
_A2UI_EXAMPLES_KEY: examples,
}
),
),
)4.2 Agent Tool Setup
The Agent uses SendA2uiToClientToolset to give the agent a tool that it can use to send A2UI to the client.
from a2ui.adk import SendA2uiToClientToolset
from a2ui.utils import resolve_catalogs
a2ui_catalogs = resolve_catalogs(self.catalog_configs, capabilities)
agent.tools = [
SendA2uiToClientToolset(
a2ui_enabled=True,
a2ui_catalogs=a2ui_catalogs,
a2ui_examples=examples,
)
]4.3 Tool Execution
Invocations of the tool in SendA2uiToClientToolset by the LLM are intercepted in the A2A Agent Executor using the A2uiEventConverter. This automatically translates tool calls into A2A Dataparts with the A2UI payload.
from a2ui.adk import A2uiEventConverter
config = A2aAgentExecutorConfig(event_converter=A2uiEventConverter())Public Guides Authoring Universal Components (`docs/public/guides/authoring-universal-components.md`)
Authoring universal components
Universal components allow you to write a custom UI component once as a Web Component and use it directly across Lit, React, Angular, and more. They work for any renderer that implements support for A2UI universal web components.
Quick start
Creating and using a universal component involves three steps:
- Define the component schema: Declare the properties your component accepts.
- Build the component: Implement the visual structure and behavior using
A2uiLitElement. - Register it in your app: Add the component to your catalog in Lit, React, Angular, or another supported renderer.
This guide uses Lit (A2uiLitElement), the recommended way to build universal components. You can also use standard Web Components without Lit; see Writing a component without Lit.
Install @a2ui/web_core, lit, and zod alongside the renderer package for your host framework (@a2ui/angular, @a2ui/react, or @a2ui/lit):
npm install @a2ui/web_core lit zod1. Define the component schema
Create an API definition with the component name and a Zod schema describing its properties. Use schemas from @a2ui/web_core/v0_9 for values that the agent can bind to data model paths or actions.
// stat-card.api.ts
import {z} from 'zod';
import {
type ComponentApi,
DynamicStringSchema,
ChildListSchema,
ActionSchema,
} from '@a2ui/web_core/v0_9';
export const StatCardApi = {
name: 'StatCard',
schema: z.object({
title: DynamicStringSchema,
value: DynamicStringSchema,
changePercentage: z.number().optional(),
trend: z.enum(['up', 'down', 'neutral']).optional(),
action: ActionSchema.optional(),
children: ChildListSchema.optional(),
}),
} satisfies ComponentApi;2. Build the component
A universal component is a class that extends A2uiLitElement, parameterized with the API from step 1 (A2uiLitElement<typeof StatCardApi>).
Your class needs to:
- Set
protected override readonly apito the API object. The base class uses its schema to resolve the incoming properties. - Implement
render()and return a Lit template built from the resolved properties. - Optionally declare
static stylesfor the component's CSS.
The base class gives you:
this.controller.props: the resolved properties. Data-bound values arrive as plain values,ActionSchemaproperties arrive as functions you call to trigger the action (props.action?.()), and dynamic properties get a setter (props.setValue()) for two-way binding.this.renderNode(childId): renders a child component referenced by your schema.
When you are done, export a WebComponentImplementation: the API, the custom element tag name, and the element class. This is the object you register in a catalog.
/* Detailed source-code truncated for AI context efficiency. */The renderer registers the element in customElements (via registerUniversalElement) before rendering a surface, so the @customElement decorator is optional. You may keep the @customElement decorator if you also use the element outside A2UI.
3. Register and use the component in your application
To use statCardComponent, add it to a catalog and pass that catalog to your renderer:
=== "Angular"
Add the component to an `AngularCatalog` and register the catalog with `provideA2Ui`. Set `useUniversalComponents: true` so the renderer mounts Web Component entries (without it they render a placeholder):
```typescript
// app.config.ts
import {ApplicationConfig} from '@angular/core';
import {
AngularCatalog,
BASIC_COMPONENTS,
BASIC_FUNCTIONS,
provideA2Ui,
} from '@a2ui/angular/v0_9';
import {statCardComponent} from './stat-card.js';
export const myCatalog = new AngularCatalog(
'my-catalog',
'0.9',
[...BASIC_COMPONENTS, statCardComponent],
BASIC_FUNCTIONS,
);
export const appConfig: ApplicationConfig = {
providers: [
provideA2Ui({
catalogs: [myCatalog],
useUniversalComponents: true,
actionHandler: (action) => console.log('Action:', action),
}),
],
};
```
Render a surface with `SurfaceComponent`. The custom element never appears in your own templates, so no `CUSTOM_ELEMENTS_SCHEMA` is needed:
```typescript
// app.component.ts
import {Component} from '@angular/core';
import {SurfaceComponent} from '@a2ui/angular/v0_9';
@Component({
selector: 'app-root',
standalone: true,
imports: [SurfaceComponent],
template: `<a2ui-v09-surface surfaceId="main" />`,
})
export class AppComponent {}
```=== "React"
Add the component to a `Catalog`, create a `MessageProcessor` with it, and render each surface with `A2uiSurface`:
```/* Detailed source-code truncated for AI context efficiency. */
You can mix React components and universal components in the same catalog and nest them in any order. React context, error boundaries, and event bubbling continue to work across universal components.
=== "Lit"
Add the component to a `Catalog`, create a `MessageProcessor` with it, and pass each surface to the `<a2ui-surface>` element:
```
/* Detailed source-code truncated for AI context efficiency. */When your application receives A2UI messages from an agent, pass them to renderer.processMessages(messages) (Angular) or processor.processMessages(messages) (React and Lit). See Connecting to Agents for transport setup and Invoking from the Agent for exposing your catalog schema to the agent.
??? tip "Testing locally without an agent"
To verify your component in the browser before connecting an agent transport, pass a sample `createSurface` and `updateComponents` payload (with a root component whose `id` is `'root'`) to `processMessages`:
```typescript
processor.processMessages([
{
version: 'v0.9',
createSurface: {surfaceId: 'main', catalogId: 'my-catalog'},
},
{
version: 'v0.9',
updateComponents: {
surfaceId: 'main',
components: [
{
id: 'root',
component: 'StatCard',
title: 'Revenue',
value: '$12,400',
changePercentage: 4.2,
trend: 'up',
},
],
},
},
]);
```Common patterns
Handling actions
When your schema includes ActionSchema, the agent can send either a server event or a client-side function call. You do not need to check which type of action was sent. In your template, call action() directly:
${props.action
? html`<button @click=${() => props.action?.()}>Open</button>`
: nothing}The agent payload sets up the action:
{
"id": "card-1",
"component": "StatCard",
"title": "Revenue",
"value": "$24,500",
"action": {
"event": {
"name": "VIEW_REVENUE_REPORT",
"context": {
"reportId": "q3-2026"
}
}
}
}When the user clicks the button, A2UI automatically resolves any context values and sends the event to the agent.
Rendering child components
If your component is a container (like a grid, list, or card), add a child property to your schema using ChildListSchema or ComponentIdSchema.
In your template, call this.renderNode(childId) for each child ID:
import {type ResolvedChildList} from '@a2ui/web_core/v0_9/universal';
override render() {
const children: ResolvedChildList = this.controller.props.children ?? [];
return html`
<div class="card-grid">
${children.map((childId) => html`
<div class="grid-item">
${this.renderNode(childId)}
</div>
`)}
</div>
`;
}this.renderNode() resolves the child component from the surface model and renders it with the appropriate catalog implementation, whether it is a basic catalog component, another universal component, or a component written for the host framework (in React, a React component renders into its own host element).
Two-way data binding for form inputs
For properties defined with dynamic schemas (such as DynamicStringSchema, DynamicNumberSchema, or DynamicBooleanSchema), A2UI automatically generates a typed setter method on props. For example, a property named value produces setValue(), while a property named checked produces setChecked().
Calling the setter writes the new value directly back to the bound data model path:
override render() {
const props = this.controller.props;
return html`
<input
type="text"
.value=${props.value ?? ''}
@input=${(e: Event) =>
props.setValue((e.target as HTMLInputElement).value)}
/>
`;
}If the agent bound value to a data model path (for example, {"path": "/user/name"}), calling props.setValue() updates that path and automatically triggers re-renders for any other components bound to the same data.
Styling and theming
Like any LitElement, an A2uiLitElement renders into a Shadow Root by default, so its static styles are encapsulated. CSS custom properties still inherit through the shadow boundary, which lets the host application theme the component without reaching inside it:
=== "Angular"
In your global `styles.css` (or on `:host` in a component stylesheet, since Angular's default emulated view encapsulation scopes component selectors away from dynamically created custom elements):
```css
/* Style custom element instances */
a2ui-stat-card {
--border-color: #3b82f6;
--card-background: #eff6ff;
}
```=== "React"
In your `App.css` or stylesheet:
```css
/* Style custom element instances */
a2ui-stat-card {
--border-color: #3b82f6;
--card-background: #eff6ff;
}
```=== "Lit"
In `MyApp`'s `static styles` (which shares a shadow root with `<a2ui-surface>`):
```typescript
static override styles = css`
a2ui-stat-card {
--border-color: #3b82f6;
--card-background: #eff6ff;
}
`;
```
If you are styling from a global page stylesheet (`index.html`) outside a Shadow DOM host like `<my-app>`, set the custom properties on `:root` or `my-app` so they inherit across `<my-app>`'s shadow boundary:
```css
my-app {
--border-color: #3b82f6;
--card-background: #eff6ff;
}
```If you want the host application's global stylesheet, design system, or utility classes (such as Tailwind or Bootstrap) to apply inside your component, opt into the Light DOM by overriding createRenderRoot():
override createRenderRoot() {
return this;
}A2uiLitElement still applies your static styles in the Light DOM. It rewrites :host to the element's tag name, scopes the remaining selectors under it, and adopts the resulting stylesheet into the document (or the enclosing shadow root) once per tag.
Writing a component without Lit
Lit simplifies data binding and event handling, but it is not required. You can achieve the same result using standard Web Components.
To build a universal component without Lit, your custom element must:
- Extend
HTMLElementand implementA2uiWebComponentElementby accepting acontextproperty (ComponentContext) from the renderer. - Create a
GenericBinder(context, schema)when connected and subscribe to resolved property updates to update the DOM. - Unsubscribe and dispose the
GenericBinderwhen disconnected or re-bound. - Export a
WebComponentImplementationwith the API,tagName, and element class.
The example below implements these steps for a StatBadgeApi with title and value properties (defined like StatCardApi in step 1):
/* Detailed source-code truncated for AI context efficiency. */Register statBadgeComponent in a catalog exactly like statCardComponent in step 3.
To render child components (after adding children: ChildListSchema.optional() to StatBadgeApi.schema), add a renderChildren() method to StatBadgeElement in stat-badge.ts and call it from render(). Reuse created elements across renders so children preserve their state:
/* Detailed source-code truncated for AI context efficiency. */Public Guides Client Setup (`docs/public/guides/client-setup.md`)
Client Setup Guide
Integrate A2UI into your application using the renderer for your platform.
Renderers
| Renderer | Platform | v0.8 | v0.9 | Status |
|---|---|---|---|---|
| React | Web | β | β | β Stable |
| Lit (Web Components) | Web | β | β | β Stable |
| Angular | Web | β | β | β Stable |
| Flutter (GenUI SDK) | Mobile/Desktop/Web | β | β | β Stable |
| Jetpack Compose | Android | β | β | π§ Planned Q2 2026 |
For more see all A2UI Renderers and Community A2UI Renderers.
Component Catalogs
A component catalog is any collection of components. A2UI provides a "Basic Catalog" but we expect you will add your own components, or shared libraries or fully replace the basic components with your own.
Your design system is what matters. You can register any collection of components and functions, and A2UI will work with them. The catalog is just the contract between your agent and your renderer.
See Defining Your Own Catalog for how to define a catalog that matches your design system.
Shared Web Library
All web renderers (Lit, Angular, React) share a common foundation: @a2ui/web_core. This library provides the message processor, state management, and data binding logic that every web renderer needs. Each framework-specific renderer builds on top of it, adding only the rendering layer for its framework.
This means core protocol handling is consistent across web platforms β only the component rendering differs.
The shared web_core library provides:
- Message Processor: Manages A2UI state and processes incoming messages.
Web Components (Lit)
npm install @a2ui/lit @a2ui/web_coreOnce installed, you can use the renderer in your app. The Lit renderer uses:
- Message Processor: Wraps the A2UI message processor.
<a2ui-surface>component: Renders surfaces in your app.- Lit Signals: Provides reactive state management for automatic UI updates.
See working example: Lit shell sample β Check its README for detailed run instructions.
Angular
npm install @a2ui/angular @a2ui/web_coreOnce installed, you can use the renderer in your app. The Angular renderer provides:
A2uiRendererService: A service that manages the A2UI message processor and reactive model.a2ui-v09-component-hostcomponent: A dynamic component host that renders A2UI components from a surface.A2UI_RENDERER_CONFIGtoken: Used to configure the renderer with catalogs and action handlers.
Setup Example (v0.9)
A2UI uses versioned imports for its protocol-specific implementations. For v0.9, configure your application providers using provideA2Ui as follows:
import {ApplicationConfig} from '@angular/core';
import {provideA2Ui, BasicCatalog} from '@a2ui/angular/v0_9';
export const appConfig: ApplicationConfig = {
providers: [
provideA2Ui({
catalogs: [new BasicCatalog()],
actionHandler: action => {
console.log('Action dispatched:', action);
},
}),
],
};Dependency Injection in Action Handler
If your actionHandler needs to inject dependencies (for example, to call a service when an action is dispatched), you can pass a factory function to provideA2Ui. Within this factory function, you can use Angular's inject() function:
import {ApplicationConfig, inject} from '@angular/core';
import {provideA2Ui, BasicCatalog} from '@a2ui/angular/v0_9';
import {MyActionDispatcherService} from './my-action-dispatcher.service';
export const appConfig: ApplicationConfig = {
providers: [
provideA2Ui(() => {
const dispatcher = inject(MyActionDispatcherService);
return {
catalogs: [new BasicCatalog()],
actionHandler: action => dispatcher.dispatch(action),
};
}),
],
};See working example: Angular samples
Streaming
By default, the Angular client uses the streaming API. To disable streaming, set the ENABLE_STREAMING environment variable to false before starting the app:
export ENABLE_STREAMING=false
yarn start restaurant[!NOTE]
Package Manager Usage: Theyarn startcommand above is specific to running the sample application within the A2UI monorepo repository. For your own regular usage and standalone projects outside this repository, use the package manager of your choice (e.g. npm, pnpm).
React
npm install @a2ui/react @a2ui/web_coreThe React renderer provides:
MessageProcessorclass: A class that manages the A2UI message processor and reactive model.<A2UISurface>component: Renders A2UI surfaces in your React appuseA2UI()hook: Accesses the message processor from any component
See working example: React shell
Flutter (GenUI SDK)
flutter pub add flutter_genuiFlutter uses the GenUI SDK which provides native A2UI rendering.
Docs: GenUI SDK | GitHub | README in GenUI Flutter Package
Connecting to Agents
Your client application needs to:
- Receive A2UI messages from the agent (via transport)
- Process messages using the Message Processor
- Send user actions back to the agent
Common transport options:
- Server-Sent Events (SSE): One-way streaming from server to client
- WebSockets: Bidirectional real-time communication
- A2A Protocol: Standardized agent-to-agent communication with A2UI support
See samples/client/lit/shell/client.ts for an example of using the A2A protocol client.
See: Transports guide
Handling User Actions
When users interact with A2UI components (clicking buttons, submitting forms, etc.), the client:
- Captures the action event from the component
- Resolves any data context needed for the action
- Sends the action to the agent
- Processes the agent's response messages
See the @a2uiaction event handler in #maybeRenderData in samples/client/lit/shell/app.ts for an example of handling button clicks and form submissions.
Error Handling
Common errors to handle:
- Invalid Surface ID: Surface referenced before
beginRendering(v0.8) orcreateSurface(v0.9) was received. - Invalid Component ID: Component IDs must be unique within a surface.
- Invalid Data Path: Check data model structure and JSON Pointer syntax.
- Schema Validation Failed: Verify message format matches A2UI specification.
See try...catch blocks in #sendMessage in samples/client/lit/shell/app.ts for examples of handling communication errors.
Next Steps
- Quickstart: Try the demo application
- Theming & Styling: Customize the look and feel
- Defining Your Own Catalog: Extend the component catalog
- Agent Development: Build agents that generate A2UI
- Reference Documentation: Deep dive into the protocol
Public Guides Defining Your Own Catalog (`docs/public/guides/defining-your-own-catalog.md`)
Defining Your Own Catalog
While the Basic Catalog is useful for starting out and bootstrapping an application, most production applications will define their own catalog to reflect their specific design system.
By defining your own catalog, you restrict the agent to using exactly the components and visual language that exist in your application, rather than generic inputs or buttons.
Why Define Your Own Catalog?
Every A2UI surface is driven by a Catalog. A catalog is simply a JSON Schema file that tells the agent which components, functions, and themes are available for it to use.
Defining your own catalog offers the following benefits:
- Design System Alignment: Restrict the agent to using exactly the components and visual language that exist in your application.
- Security and Type Safety: You register entire catalogs with your client application, ensuring that only trusted components are rendered.
- No Mappers Needed: It is recommended to build catalogs that directly reflect your client's design system rather than trying to map a generic catalog (like the Basic Catalog) to it through an adapter.
The Basic Catalog is just one example and is intentionally sparse to remain easily implementable by different renderers.
How It Works
- Define the Catalog: Create a catalog definition (JSON Schema) listing the components, functions, and styles your application supports.
- Register the Catalog: Register the catalog and its corresponding component implementations (renderers) with your client application.
- Announce Support: The client informs the agent which catalogs it supports (via
supportedCatalogIds). - Agent Selects Catalog: The agent chooses a catalog for a given UI surface (via
catalogIdinside the creation message, likecreateSurface). - Agent Generates UI: The agent generates component messages using the components defined in that catalog by name.
Implementation Guide
It is recommended to create catalogs that directly map to your existing component library.
=== "Web (Lit / Angular / React)"
To implement your own catalog on the web:
- Create a JSON Schema containing your component definitions.
- Create your own `Component` objects and `Catalog` object within your chosen web renderer.
- Provide the schema or reference ID to the agent.
_Detailed guides for each framework coming soon._=== "Flutter"
To implement your own catalog in Flutter:
- Define a JSON Schema describing your widget properties.
- Map the schema to Flutter widgets using a custom renderer.
*Detailed Flutter integration guide coming soon.*Security Considerations
When defining and registering catalogs:
- Allowlist components: Only register components you trust in your catalog definition. Don't expose components that offer dangerous capabilities (e.g., executing arbitrary scripts) unless strictly controlled.
- Validate properties: Always validate component properties from agent messages to ensure they match expected type constraints.
- Sanitize text: Avoid rendering un-sanitized content provided by the agent unless safe bounds are established.
Next Steps
- Theming & Styling: Customize the look and feel of components.
- Component Reference: Explore standard types that might be available for reuse.
- Agent Development: Build agents that interact with your Catalog.
Public Guides Mcp Apps In A2ui (`docs/public/guides/mcp-apps-in-a2ui.md`)
MCP Apps Integration in A2UI Surfaces
This guide explains how Model Context Protocol (MCP) Applications are integrated and displayed within the A2UI surface, along with the security model and testing guidelines.
NOTE: Looking for the core A2UI-over-MCP protocol? See A2UI over MCP for how to return A2UI JSON payloads from MCP tool calls.
Overview
The Model Context Protocol (MCP) allows MCP servers to deliver rich, interactive HTML-based user interfaces to hosts. A2UI provides a secure environment to run these third-party applications.
Double-Iframe Isolation Pattern
To run untrusted third-party code securely, A2UI utilizes a double-iframe isolation pattern. This approach isolates raw DOM injection from the main application while maintaining a structured JSON-RPC channel.
Security Rationale
Standard single-iframe sandboxing with allow-scripts is often bypassed if combined with allow-same-origin, which defeats the containerization. Any iframe with allow-scripts and allow-same-origin can escape its sandbox by programmatically interacting with its parent DOM or removing its own sandbox attribute.
To prevent this, A2UI strictly excludes allow-same-origin for the inner iframe where the third-party application runs.
The Architecture
- Sandbox Proxy (
sandbox.html): An intermediateiframeserved from the same origin. It isolates raw DOM injection from the main app while maintaining a structured JSON-RPC channel.- Permissions: Do not sandbox in the host template (e.g.,
mcp-app.tsormcp-apps-component.ts). - Host origin validation: Validates that messages come from the expected host origin.
- Permissions: Do not sandbox in the host template (e.g.,
- Embedded App (Inner Iframe): The innermost
iframe. Injected dynamically viasrcdocwith restricted permissions.- Permissions:
sandbox="allow-scripts allow-forms allow-popups allow-modals"(MUST NOT includeallow-same-origin,allow-top-navigation, orallow-top-navigation-by-user-activation). - Isolation: Removes access to
localStorage,sessionStorage,IndexedDB, and cookies due to unique origin. - Top-level window hijacking defense: Omitting
allow-top-navigationandallow-top-navigation-by-user-activationprevents embedded scripts from executing frame-busting attacks (such aswindow.top.location = "...") to redirect the host window away. - Hyperlink exfiltration defense: Omitting
allow-popupsand capturing link navigations prevents untrusted content from exfiltrating data via clickjacking into spawned windows.
- Permissions:
Physical Iframe Nesting
flowchart TD
subgraph "Host Application"
A[A2UI Page] --> B["Host Component e.g., McpApp"]
end
subgraph "Sandbox Proxy"
B -->|Message Relay| C[iframe sandbox.html]
end
subgraph "Embedded App"
C -->|Dynamic Injection| D[inner iframe untrusted content]
endEnd-to-End Architecture & Lifecycle Flow
The complete cycleβincluding layout tree hierarchy, completely separated backend actors (the Proxy Agent and the MCP Server), and how isolated third-party widgets interact reactively with their native siblings (e.g., the Pong game's scoreboard)βis detailed below:
graph TD
%% 1. Top Tier (Strict vertical hierarchy)
MCPServer["MCP App Server<br/>(Hosts widget resources & core tools)"]
%% 2. Middle-Top Tier
Agent["AI Agent<br/>(A2UI Backend Coordinator)"]
%% 3. Subgraph for Host layout tree (Bottom Tier)
subgraph HostApp ["Host Application"]
direction TB
Shell["A2UI Rendering Engine & State Manager<br/>(Orchestrates native layout & state bindings)"]
subgraph LayoutTree ["A2UI Component Tree"]
McpComponent["McpApp Component<br/>(Sandboxed HTML/JS Widget)"]
SiblingComponent["Other A2UI Components<br/>(e.g., PongScoreBoard)"]
end
Shell <-->|"1. Initialize postMessage Event Bridge"| McpComponent
Shell -.->|"5. Reactive State Update<br/>(e.g., Render updated score)"| SiblingComponent
end
%% Vertical Channel connecting Top to Middle-Top
MCPServer ==>|"MCP Protocol (SSE / Stdio)"| Agent
%% Unidirectional Data Cycle (Flowing vertically through the center)
McpComponent ==>|"2. Tool Action Request<br/>(e.g., score_update)"| Shell
Shell ==>|"3. Action Delegation (A2UI Protocol)"| Agent
Agent ==>|"4. State Mutation & Sync (dataModelUpdate)"| Shell
%% Style Sibling Relationship
McpComponent -.->|"No Direct Access (Strictly Isolated)"| SiblingComponentHow the Sibling Update Loop Works:
- Initialize postMessage Event Bridge (1): The host shell instantiates the double-iframe sandbox and establishes a secure message relay bridge with the
McpAppcomponent. - Tool Action Request (2): When a user interacts with the sandboxed app (e.g., scores a point in the Pong game), the app triggers a tool action by posting a message over the postMessage bridge.
- Action Delegation (3): The host layout engine intercepts the action and delegates its execution to the
AI Proxy Agentover the A2UI/A2A protocol. The agent optionally coordinates with theMCP App Serverusing the standard MCP Protocol (SSE / Stdio) if external calculation or resources are required. - State Mutation & Sync (4): The agent processes the action, mutates the master session state, and pushes a
dataModelUpdateback to the host state manager. - Reactive State Update (5): The host updates its local store, triggering a reactive update on sibling A2UI components (such as native scoreboards or displays) bound to that state path. Direct communication between the sandboxed component and native sibling elements is strictly blocked to maintain containerization security.
Usage / Code Example
The MCP Apps component typically resolves to a custom node in the A2UI catalog. Here is how a developer might use it in their code.
1. Register within the Catalog
You must register the component in your catalog application. For example, in Angular:
import {Catalog} from '@a2ui/web_core/v0_9';
import {z} from 'zod';
import {McpApp} from './mcp-app';
import {Button} from './button';
import {Snackbar} from './snackbar';
const McpAppSchema = z.object({
content: z.union([z.string(), z.object({id: z.string()})]).optional(),
allowedTools: z.array(z.string()).optional(),
title: z.string().optional(),
});
export const DEMO_CATALOG = new Catalog(
'my_app.org/some_catalog.json',
'v0.9',
[
{name: 'McpApp', component: McpApp, schema: McpAppSchema},
{
name: 'Button',
component: Button,
schema: z.object({
label: z.string(),
action: z.any().optional(),
}),
},
{
name: 'Snackbar',
component: Snackbar,
schema: z.object({
message: z.string(),
durationMs: z.number().default(3000),
}),
},
]
);2. Usage in A2UI Message
In the Host or Agent context, send an A2UI message that translates to this custom node.
{
"type": "custom",
"name": "McpApp",
"properties": {
"content": "<h1>Hello, World!</h1>",
"title": "My MCP App"
}
}If the content is complex or requires encoding, you can pass a URL-encoded string:
{
"type": "custom",
"name": "McpApp",
"properties": {
"content": "url_encoded:%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E",
"title": "My MCP App"
}
}Communication Protocol
Communication between the Host and the embedded inner iframe is facilitated via a structured JSON-RPC channel over postMessage.
- Events: The Host Component listens for a
SANDBOX_PROXY_READY_METHODmessage from the proxy. - Bridging: An
AppBridgehandles message relaying. Developers (specifically the MCP App Developer inside the untrusted iframe) can call tools on the MCP server usingbridge.callTool(). - The Host: Resolves callbacks (e.g., specific resizing, Tool results).
Limitations
Because allow-same-origin is strictly omitted for the innermost iframe, the following conditions apply:
- The MCP app cannot use
localStorage,sessionStorage,IndexedDB, or cookies. Each application runs with a unique origin. - Direct DOM manipulation by the parent is blocked. All interactions must proceed via message passing.
Prerequisites
To run the samples, ensure you have the following installed:
- Python 3.10+ β Required for the agent and MCP server backends
- uv β Fast Python package manager (used to run all Python samples)
- Node.js 18+ and Yarn β Required for building and running the sample client apps within this monorepo workspace.
- A
GEMINI_API_KEYβ Required by all ADK-based agents. Get one from Google AI Studio
[!NOTE]
Package Manager Usage: Running the built-in sample applications within the A2UI repository requires Yarn as configured by Corepack workspaces. For your own regular usage and standalone projects outside this repository, use the package manager of your choice (e.g. npm, pnpm).
β οΈ Environment variable setup: You can either export
GEMINI_API_KEYin your shell or create a.envfile in each agent directory. The agents usedotenvto load.envfiles automatically.bash# Option 1: Export in shell export GEMINI_API_KEY="your-api-key-here" # Option 2: Create .env file in the agent directory echo 'GEMINI_API_KEY=your-api-key-here' > .env
Samples
There are two primary samples demonstrating MCP Apps integration. Each sample requires running multiple terminals β one for each backend service and one for the client.
Sample 1: MCP App Standalone Sample (Lit Client & ADK Agent)
This sample verifies the sandbox with a Lit-based client and an ADK-based A2A agent.
Step 1: Start the Agent
In a separate terminal, navigate to the agent directory and start the agent:
cd samples/agent/adk/mcp-apps-in-a2ui-sample
uv run agent.pyThe agent will run on http://localhost:8000.
Step 2: Start the Client
In a new terminal, navigate to the client directory and start the dev server (requires building the Lit renderer first):
cd samples/client/lit/mcp-apps-in-a2ui-sample
yarn install
yarn devThe client starts at http://localhost:5173/.
Step 3: Open in Browser
Open your browser and navigate to http://localhost:5173/. You should see the A2UI interface loading the MCP App.
What to expect: A page loading the MCP App in a sandboxed iframe. Clicking the "Call Agent Tool" button inside the iframe will trigger an action that is handled by the agent.
Sample 2: MCP Apps (Calculator + Pong) (Angular Client + MCP Server + Proxy Agent)
This sample verifies the sandbox with an Angular-based client, an MCP Proxy Agent, and a remote MCP Server. It requires three backend processes.
Step 1: Start the MCP Server (Calculator)
cd samples/community/mcp/mcp-apps-calculator/
uv run .The MCP server starts on http://localhost:8000 (or another port if 8000 is occupied, e.g. uv run . --port 8001) using SSE transport.
Step 2: Start the MCP Apps Proxy Agent
In a new terminal:
cd samples/community/agent/adk/mcp_app_proxy/
export GEMINI_API_KEY="your-key" # or use a .env file
uv run .The proxy agent starts on http://localhost:10006 by default.
Step 3: Build and Start the Angular Client
First, build the renderer packages by running yarn build:all at the repository root directory:
# Run at repository root
yarn build:allThen, in a new terminal, navigate to the client directory, install local dependencies, and start the app (which automatically bundles the sandbox iframe proxy and starts the development server):
# Navigate to the client directory
cd samples/community/client/angular/
# Install local dependencies
yarn install
# Start the app and bundle sandbox
yarn start mcp_calculatorβ οΈ
yarn build:allis required:yarn build:allcompiles the A2UI renderer packages that the Angular app depends on. Runningyarn start mcp_calculatorautomatically bundles the sandbox proxy into the Angular project's public assets before starting the server.
The client starts at http://localhost:4200/.
Step 4: Open in Browser
Navigate to:
http://localhost:4200/?disable_security_self_test=trueWhat to expect: A set of smart chips to load calculator app or pong app will be rendered. Both apps run in their own sandboxed iframes.
| Calculator App | Pong App |
|---|---|
URL Options for Testing
For testing purposes, you can opt-out of the security self-test by using specific URL query parameters.
`disable_security_self_test=true`
This query parameter allows you to bypass the security self-test that verifies iframe isolation. This is useful for debugging and testing environments where the double-iframe setup may not pass strict origin checks (e.g., localhost development).
Example usage:
http://localhost:4200/?disable_security_self_test=trueTroubleshooting
| Problem | Solution |
|---|---|
GEMINI_API_KEY environment variable not set |
Export the key or add a .env file in the agent directory |
Python version error on restaurant_finder agent |
Install Python 3.13+ (required by that sample's pyproject.toml) |
yarn build:renderer fails |
Make sure you ran yarn install first in samples/client/lit/ |
| Angular client shows blank page | Ensure you ran yarn build:sandbox before yarn start |
| MCP app iframe doesn't load | Check that both the MCP server (port 8000) and proxy agent (port 10006) are running |
ng serve not found |
Run yarn install to install dev dependencies including @angular/cli |
| "URL with hostname not allowed" | Angular 21 restricts allowed hosts. Use localhost (the default) β do not pass --host 0.0.0.0 |
| Security self-test fails in dev | Add ?disable_security_self_test=true to the URL |
Public Guides Renderer Development (`docs/public/guides/renderer-development.md`)
A2UI Renderer Implementation Guide
This document outlines the required features for a new renderer implementation of the A2UI protocol. It is intended for developers building new renderers (e.g., for React, Flutter, iOS, etc.).
NOTE: Version-Aware Guide
This guide provides implementation checklists for v0.8, v0.9.1 (Current Production), and v1.0 (Candidate). Use the tabs below to select the version you are targeting.
Web Renderers: Use `@a2ui/web_core` (`web_core`)
If you're building a renderer for the web (React, Vue, Svelte, etc.), you don't need to implement message processing, state management, or schema validation from scratch. The @a2ui/web_core package (web_core) provides all the framework-agnostic logic that the maintained Lit, Angular, and React renderers share.
What `web_core` provides
| Module | What it does |
|---|---|
MessageProcessor |
Processes the A2UI JSONL stream, dispatches messages, manages surface lifecycle |
SurfaceModel / SurfaceGroupModel |
State management for surfaces, components, and data models |
DataModel / DataContext |
Data binding resolution, path-based lookups, template list rendering |
ComponentModel |
Component tree state, adjacency list β tree resolution |
| Types & Schemas | TypeScript types for all A2UI components, primitives, colors, styles, and JSON schema validation |
| Expression parser | Client-side function evaluation (v0.9+) |
How the maintained renderers use it
All three web renderers follow the same pattern β web_core handles the protocol, the renderer handles the UI:
// Types β shared across all renderers
import type * as Types from '@a2ui/web_core/types/types';
import type * as Primitives from '@a2ui/web_core/types/primitives';
// v0.8: Message processing and state
import {A2uiMessageProcessor} from '@a2ui/web_core/data/model-processor';
// v0.9.1 / v1.0: Message processing, surfaces, catalogs
import {MessageProcessor} from '@a2ui/web_core/v0_9';
import {SurfaceModel} from '@a2ui/web_core/v0_9';
// Styles and layout helpers
import * as Styles from '@a2ui/web_core/styles/index';Your renderer only needs to:
- Map A2UI component types to your framework's components (e.g.,
Textβ, `Button` β) - Subscribe to state changes from
web_coreand re-render - Forward user actions back through the
MessageProcessor
See the React renderer, Lit renderer, and Angular renderer for working examples of this pattern.
Version support
web_core exports API sets per version:
@a2ui/web_core/v0_8β stable v0.8@a2ui/web_core/v0_9β v0.9/v0.9.1 support withcreateSurface, custom catalogs, client-side functions@a2ui/web_core/v1_0β candidate v1.0 support including RPC action responses
TIP: Start with
web_coreBuilding a web renderer without
web_coremeans reimplementing ~3,000 lines of message processing, state management, and schema validation. Unless you have a specific reason to diverge, use it.
I. Core Protocol Implementation Checklist
This section details the fundamental mechanics of the A2UI protocol. A compliant renderer must implement these systems to successfully parse the server stream, manage state, and handle user interactions.
=== "v0.8"
- **JSONL Stream Parsing**: Read a streaming response line by line, decoding each line as a distinct JSON object.
- **Message Dispatcher**: Identify message types (`beginRendering`, `surfaceUpdate`, `dataModelUpdate`, `deleteSurface`) and route to the correct handler.
- **Surface Management**:
- Key surfaces by `surfaceId`.
- Handle `surfaceUpdate`: Add/update components in the surface's buffer.
- Handle `deleteSurface`: Remove the surface and all associated data/components.
- **Component Buffering**:
- Maintain a component buffer (e.g., `Map<String, Component>`) for each surface.
- Reconstruct the UI tree by resolving `id` references (`children.explicitList`, `child`, `contentChild`, etc.).
- **Data Model Store**:
- Maintain data model state for each surface.
- Handle `dataModelUpdate`: Update values at paths using the adjacency list representation (`[{ "key": "name", "valueString": "Bob" }]`).
- **Progressive Rendering**:
- Buffer updates until `beginRendering` is received.
- Start rendering from the specified `root` ID upon receiving `beginRendering`. Apply theme styles.
- **Data Binding Resolution**:
- Resolve `BoundValue` objects using `literalString` / `literalNumber` / `path`.
- **Dynamic Lists**:
- For `children.template`, iterate over the data list at `template.dataBinding` and render components using `template.componentId`.
- **Client-to-Server**:
- Dispatch `userAction` containing context with resolved paths to the server.
- Include `a2uiClientCapabilities` in transport metadata.=== "v0.9.1 (Current)"
- **JSONL Stream Parsing**: Read a streaming response line by line, decoding each line as a distinct JSON object.
- **Message Dispatcher**: Identify message types (`createSurface`, `updateComponents`, `updateDataModel`, `deleteSurface`) and route to the correct handler.
- **MIME Type Validation**: Intercept payloads based on the standardized `application/a2ui+json` MIME type.
- **Surface Management**:
- Key surfaces by `surfaceId`.
- Handle `createSurface`: Create surface, bind `catalogId`, register `theme` and `sendDataModel`.
- Handle `updateComponents`: Add/update components in flat format using `"component": "Type"` discriminators.
- Handle `deleteSurface`: Remove the surface and all associated data/components.
- **Component Buffering**:
- Maintain a component buffer (e.g., `Map<String, Component>`) for each surface.
- Reconstruct the UI tree by resolving ID references in container component `children` arrays or `child` fields.
- **Data Model Store**:
- Maintain data model state for each surface.
- Handle `updateDataModel`: Update data at paths using standard JSON objects with upsert semantics.
- **Progressive Rendering**:
- Render immediately upon parsing a valid root component (ID `root`) in `updateComponents`. Do not wait for a special rendering signal.
- **Data Binding Resolution**:
- Resolve simplified bound values (either literals or `{"path": "..."}`).
- **Dynamic Lists**:
- For child templates, iterate over data arrays at `path` and render templates specified by `componentId`.
- **Client-side Functions**:
- Evaluate registered catalog-defined functions (e.g. `formatString` interpolation).
- **Client-to-Server**:
- Dispatch `action` (replaces `userAction`) containing context with resolved paths.
- Automatically include the full client-side data model in the metadata if `sendDataModel` was requested.
- Send structured `ValidationFailed` error messages to the server if a schema validation fails.=== "v1.0 (Candidate)"
All requirements from v0.9.1, with the following extensions:
- **Surface Properties**:
- Handle `createSurface` with `surfaceProperties` (renamed from `theme`). Custom primary brand colors are no longer supported inside the surface schema.
- **Directional Function Calls (RPC)**:
- Handle `callRendererFunction` messages from the agent and return `rendererFunctionResponse` (or `error`).
- **Client-to-Server**:
- Support initiating `callAgentFunction` messages to the agent for remote function execution.
- If using A2A, every A2A `Message` sent to the server must include an `a2uiClientCapabilities` object in the `metadata` field of the A2A `Message`.
- **Capabilities**:
- Expose `surfaceProperties` in place of `theme` during capabilities exchange.II. Basic Component Catalog Checklist
To ensure a consistent user experience across platforms, A2UI defines a basic set of components. Your client should map these abstract definitions to their corresponding native UI widgets.
=== "v0.8"
- **Text**: Render text. Support `usageHint` (h1-h5, body, caption).
- **Image**: Render image from URL. Support `fit` and `usageHint` (avatar, hero, etc.).
- **Icon**: Render system icons.
- **Video**: Render video player.
- **AudioPlayer**: Render audio player with description.
- **Divider**: Render horizontal/vertical lines.
- **Row** / **Column**: Arrange children horizontally/vertically. Support `distribution` and `alignment`. Support child `weight`.
- **List**: Render scrollable list.
- **Card**: Box layout with rounded corners and shadows.
- **Tabs**: Tabbed navigation using `tabItems`.
- **Modal**: Popup triggered by `entryPointChild` displaying `contentChild`.
- **Button**: Clickable button triggering `userAction`. Support `primary` variant.
- **CheckBox**: Boolean checkbox.
- **TextField**: Input field supporting `label`, `textFieldType` (`shortText`, `longText`, etc.), and `validationRegexp`.
- **MultipleChoice**: Support `options`, `maxAllowedSelections`, and single/multiple value selections.
- **Slider**: Support ranges using `minValue`, `maxValue`.=== "v0.9.1 (Current)"
- **Text**: Render text. Support `variant` (replaces `usageHint`).
- **Image**: Render image from URL. Support `fit` and `variant`.
- **Icon**: Render system icons.
- **Video**: Render video player.
- **AudioPlayer**: Render audio player with description.
- **Divider**: Render horizontal/vertical lines.
- **Row** / **Column**: Arrange children horizontally/vertically. Support `justify` and `align`. Support child `weight`.
- **List**: Render scrollable list.
- **Card**: Box layout with rounded corners and shadows.
- **Tabs**: Tabbed navigation using `tabs`.
- **Modal**: Popup triggered by `trigger` displaying `content`.
- **Button**: Clickable button triggering `action`. Support `variant` (primary, borderless).
- **CheckBox**: Boolean checkbox.
- **TextField**: Input field supporting `label`, `value` (replaces `text`), `variant` (`shortText`, `longText`, etc.), and `checks` (validation functions).
- **ChoicePicker**: (Replaces MultipleChoice) Support `options` and `variant` (`mutuallyExclusive`, `multipleSelection`).
- **Slider**: Support ranges using `min`, `max` (replaces `minValue`, `maxValue`).=== "v1.0 (Candidate)"
All components from v0.9.1, with the following extensions:
- **Video**: Support the `posterUrl` property to display a preview image.
- **TextField**: Support the `placeholder` property.Public Guides Theming (`docs/public/guides/theming.md`)
Theming & Styling
Customize the look and feel of A2UI components to match your brand.
The A2UI Styling Philosophy
A2UI follows a renderer-controlled styling approach by default, but allows for flexibility through catalogs:
- Agents describe what to show (components and structure)
- Renderers decide how it looks (colors, fonts, spacing)
However, the protocol is flexible enough to allow agents to influence styling when needed.
Styling Layers
A2UI styling works in layers:
flowchart TD
A["Agent-provided styling<br/>Semantic hints and theme data<br/>(e.g., usageHint: 'h1')"]
B["Catalog Theming<br/>Catalog interprets theme data<br/>or uses defaults"]
C["Platform theming<br/>Developer customizes<br/>(CSS variables, stylesheets...)"]
D["Rendered Output"]
A --> B --> C --> DAgent-provided styling information
Semantic hints
Agents provide semantic hints (not visual styles) to guide rendering. In the basic catalog:
{
"id": "title",
"component": {
"Text": {
"text": {"literalString": "Welcome"},
"usageHint": "h1"
}
}
}Common usageHint values:
- Text:
h1,h2,h3,h4,h5,body,caption - Other components have their own hints (see Component Reference)
The catalog elements map these semantic hints to actual components on the target platform, and styles them.
`theme` property
The A2UI protocol allows for an arbitrary theme property in the createSurface message. For now, this property is
defined as z.any().optional() in the Zod schema, meaning the agent can pass any JSON structure that the client
renderer and catalog understand.
- See the schema definition in server-to-client.ts.
- See the
Catalogclass andthemeSchemain catalog/types.ts.
Note: The basic catalog components are not wired to use the theme coming from the agent.
Want to influence this design? Chime in here: #1118.
Catalog theming
Theming is a responsibility of the catalog implementation. Each catalog can provide whatever theming solution it wants.
As an example, this is how the default basic catalog does it:
The Web Basic Catalog Theming
On the web, the basic catalog provided by the default A2UI renderers is themed by overriding CSS variables.
Basic catalog components inject a small stylesheet with default values for these variables. The stylesheet targets:where(:root) so their specificity is minimal, and the host app can override them easily.
For example, to override the primary color, you can simply add this to your app's CSS:
:root {
--a2ui-color-primary: #ff5722;
}See the default styles in default.ts.
See some examples per-platform:
Per-component overrides
Beyond global theming, each component of the basic catalog exposes custom variables to further refine its appearance.
For example, the Card component exposes a --a2ui-card-background variable.
Check the documentation of each component to see what variables it exposes.
Common Styling Features
Dark Mode
The default web renderers support automatic dark mode based on system preferences (prefers-color-scheme).
To always force dark or light mode (or to programmatically control switching), use the classnames a2ui-light ora2ui-dark in an ancestor element of the generated code.
Custom Fonts
Fonts can be loaded as in any other web application. The basic catalog components attempt to inherit the font family
of their container, but offer two overridable values: --a2ui-font-family-title and --a2ui-font-family-monospace to
set a different font for headings and monospace text blocks.
Flutter
Flutter has built-in theming support. See:
- Use themes to share colors and font styles from the Flutter docs.
Best Practices
1. Use Semantic Hints, Not Visual Properties
When defining your components, agents should provide semantic hints (usageHint), never visual styles:
// β
Good: Semantic hint
{
"component": {
"Text": {
"text": {"literalString": "Welcome"},
"usageHint": "h1"
}
}
}
// β Bad: Visual properties (not supported)
{
"component": {
"Text": {
"text": {"literalString": "Welcome"},
"fontSize": 24,
"color": "#FF0000"
}
}
}2. Maintain Accessibility
- Ensure sufficient color contrast (WCAG AA: 4.5:1 for normal text, 3:1 for large text)
- Test with screen readers
- Support keyboard navigation
- Test in both light and dark modes
3. Use Design Tokens
Define reusable design tokens (colors, spacing, etc.) and reference them throughout your styles for consistency.
4. Test Across Platforms
- Test your theming on all target platforms (web, mobile, desktop)
- Verify both light and dark modes
- Check different screen sizes and orientations
- Ensure consistent brand experience across platforms
Next Steps
- Defining Your Own Catalog: Build custom components with your styling
- Component Reference: See styling options for all components
- Client Setup: Set up the renderer in your app
Public Introduction Agent Ui Ecosystem (`docs/public/introduction/agent-ui-ecosystem.md`)
How Does A2UI Compare?
The agentic UI space is evolving rapidly. Here's how A2UI relates to the other major approaches.
At a Glance
| A2UI | MCP Apps | AG-UI | |
|---|---|---|---|
| Approach | Declarative component blueprints | Pre-built HTML via ui:// URIs |
High-bandwidth protocol connecting backends to frontends |
| Rendering | Native components (Angular, Flutter, Lit, etc.) | Sandboxed iframe |
Developer-defined (any framework) |
| Styling | Host app controls: inherits design system | Isolated: remote server controls appearance | Developer controls: part of the host app |
| Security | Declarative data, no code execution | Sandboxed iframe isolation | Trusted code within your own app |
| Multi-agent | β Across trust boundaries | β Multiple MCP servers | β οΈ Primarily single-agent |
| Cross-platform | β Web, mobile, desktop, native | β οΈ Web-focused (iframe) | β Protocol is framework-agnostic |
| LLM generation | β Designed for streaming output | β Pre-built by server | β Via A2UI integration |
| Spec | Open protocol (Apache 2.0) | MCP extension (SEP-1865) | Open source (by CopilotKit) |
A2UI vs MCP Apps
MCP Apps treat UI as a resource: servers provide pre-built HTML via ui:// URIs, rendered in sandboxed iframes. The remote integration controls all content and appearance, with configuration happening through tool calling. A2UI takes a declarative UI approach: agents send component blueprints, but the host application controls styling, theming, and how those components are configured and rendered. Choose MCP Apps when the server should own the full UI experience; choose A2UI when you want dynamic, cross-platform UI that fits naturally into your app.
A2UI vs AG-UI / CopilotKit
AG-UI is a transport protocol connecting agent backends to frontends with real-time state sync. A2UI is a UI format: the payload that describes what to render. They're complementary: use AG-UI as the pipe, A2UI as the content. AG-UI is a project by the CopilotKit team. AG-UI has day-zero A2UI compatibility.
A2UI vs ChatKit (OpenAI)
ChatKit offers a tightly integrated experience within the OpenAI ecosystem. A2UI shares some design philosophy with ChatKit: both define a set of basic components and use a configurable, declarative abstraction layer. A2UI is platform-agnostic and designed for developers building their own agentic surfaces across web, mobile, and desktop, or for multi-agent systems where agents need to render UI across trust boundaries.
Using Them Together
These approaches are complementary, not competing:
- A2UI + AG-UI: AG-UI as transport, A2UI as the generative UI format.
- A2UI + A2A: A2UI messages sent via the A2A protocol for multi-agent systems.
- A2UI + MCP: Upcoming bridge lets MCP servers provide A2UI blueprints alongside HTML resources.
Further Reading
For more information, see the following resources:
- What is A2UI?: Protocol overview.
- Transports: How A2UI messages travel between agents and clients.
- Where is A2UI Used?: Case studies and adopters.
Public Introduction How To Use (`docs/public/introduction/how-to-use.md`)
How to Use A2UI
Choose the integration path that matches your role and use case.
Three Paths
Path 1: Building a Host Application (Frontend)
Integrate A2UI rendering into your existing app or build a new agent-powered frontend.
Choose a renderer:
- Web: Lit, Angular, React.
- Mobile/Desktop: Flutter GenUI SDK.
Quick setup:
For Angular:
npm install @a2ui/angular @a2ui/web_coreFor React:
npm install @a2ui/react @a2ui/web_coreConnect to agent messages (SSE, WebSockets, or A2A) and customize styling to match your brand.
Next: Client Setup Guide | Theming
Path 2: Building an Agent (Backend)
Create an agent that generates A2UI responses for any compatible client.
Choose your framework:
- Python: Google ADK, LangChain, custom.
- Node.js: A2A SDK, Vercel AI SDK, custom.
Include the A2UI schema in your LLM prompts, generate JSONL messages, and stream to clients over SSE, WebSockets, or A2A.
Next: Agent Development Guide
Path 3: Using an Existing Framework
Use A2UI through frameworks with built-in support:
- AG-UI / CopilotKit - Full-stack agentic app framework with A2UI rendering.
- Flutter GenUI SDK - Cross-platform generative UI (uses A2UI internally).
Next: Agent UI Ecosystem | Where is A2UI Used?
Public Introduction What Is A2ui (`docs/public/introduction/what-is-a2ui.md`)
What is A2UI?
A2UI (Agent to UI) is a declarative UI protocol for agent-driven interfaces. AI agents generate rich, interactive UIs that render natively across platforms (web, mobile, desktop) without executing arbitrary code.
The Problem
Text-only agent interactions are inefficient:
User: "Book a table for 2 tomorrow at 7pm"
Agent: "Okay, for what day?"
User: "Tomorrow"
Agent: "What time?"
...Better: Agent generates a form with date picker, time selector, and submit button. Users interact with UI, not text.
The Challenge
In multi-agent systems, agents often run remotely (different servers, organizations). They can't directly manipulate your UIβthey must send messages.
Traditional approach: Send HTML/JavaScript in iframes.
- Heavy, visually disjointed.
- Security complexity.
- Doesn't match app styling.
Need: Transmit UI that's safe like data, expressive like code.
The Solution
A2UI: JSON messages describing UI that:
- LLMs generate as structured output.
- Travel over any transport (A2A, AG-UI, SSE, WebSockets).
- Client renders using its own native components.
Result: Client controls security and styling, agent-generated UI feels native.
Example
=== "v0.8 (Legacy)"
```jsonl
{
"surfaceUpdate": {
"surfaceId": "booking",
"components": [
{
"id": "title",
"component": {
"Text": {
"text": { "literalString": "Book Your Table" },
"usageHint": "h1"
}
}
},
{
"id": "datetime",
"component": {
"DateTimeInput": {
"value": { "path": "/booking/date" },
"enableDate": true
}
}
},
{
"id": "submit-text",
"component": {
"Text": {
"text": { "literalString": "Confirm" }
}
}
},
{
"id": "submit-btn",
"component": {
"Button": {
"child": "submit-text",
"action": { "name": "confirm_booking" }
}
}
}
]
}
}
{
"dataModelUpdate": {
"surfaceId": "booking",
"contents": [
{
"key": "booking",
"valueMap": [
{ "key": "date", "valueString": "2025-12-16T19:00:00Z" }
]
}
]
}
}
{
"beginRendering": {
"surfaceId": "booking",
"root": "title"
}
}
```=== "v0.9 (Stable)"
```jsonl
{
"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": "title",
"component": "Text",
"text": "Book Your Table",
"variant": "h1"
},
{
"id": "datetime",
"component": "DateTimeInput",
"value": { "path": "/booking/date" },
"enableDate": true
},
{
"id": "submit-text",
"component": "Text",
"text": "Confirm"
},
{
"id": "submit-btn",
"component": "Button",
"child": "submit-text",
"variant": "primary",
"action": {
"event": { "name": "confirm_booking" }
}
}
]
}
}
{
"version": "v0.9.1",
"updateDataModel": {
"surfaceId": "booking",
"path": "/booking",
"value": {
"date": "2025-12-16T19:00:00Z"
}
}
}
```
A2UI messages use `createSurface` to initialize the surface, a flat component structure, and include a `version` field.Client renders these messages as native components (Angular, Flutter, React, etc.).
Core Value
1. Security: Declarative data, not code. Agent requests components from client's trusted catalog. No code execution risk.
2. Native Feel: No iframes. Client renders with its own UI framework. Inherits app styling, accessibility, performance.
3. Portability: One agent response works everywhere. Same JSON renders on web (Lit/Angular/React), mobile (Flutter/SwiftUI/Jetpack Compose), desktop.
Design Principles
1. LLM-Friendly: Flat component list with ID references. Easy to generate incrementally, correct mistakes, stream.
2. Framework-Agnostic: Agent sends abstract component tree. Client maps to native widgets (web/mobile/desktop).
3. Separation of Concerns: Three layersβUI structure, application state, client rendering. Enables data binding, reactive updates, clean architecture.
What A2UI Is NOT
- Not a framework (it is a protocol).
- Not a replacement for HTML (for agent-generated UIs, not static sites).
- Not a robust styling system (client controls styling with limited server-side styling support).
- Not limited to web (works on mobile and desktop).
Key Concepts
A2UI relies on the following key concepts:
- Surface: Canvas for components (dialog, sidebar, main view).
- Component: UI element (Button, TextField, Card, etc.).
- Data Model: Application state, components bind to it.
- Catalog: Available component types.
- Message: JSON object (
surfaceUpdate,dataModelUpdate,beginRendering, etc.).
For a comparison of similar projects, see Agent UI Ecosystem.
Public Introduction Who Is It For (`docs/public/introduction/who-is-it-for.md`)
Who is A2UI For?
Developers building AI agents with rich, interactive UIs.
Three Audiences
1. Host App Developers (Frontend)
Build multi-agent platforms, enterprise assistants, or cross-platform apps where agents generate UI.
Why A2UI:
- Brand control: client owns styling and design system.
- Multi-agent: support local, remote, and third-party agents.
- Secure: declarative data, no code execution.
- Cross-platform: web, mobile, desktop.
- Interoperable: open source, same spec with multiple renderers.
Get started: Client Setup | Theming | Defining Your Own Catalog
2. Agent Developers (Backend/AI)
Build agents that generate forms, dashboards, and interactive workflows.
Why A2UI:
- LLM-friendly: flat structure, easy to generate incrementally.
- Rich interactions: beyond text (forms, tables, visualizations).
- Generations not tools: UI as part of the generated output from the agent.
- Portable: one agent response works across all A2UI clients.
- Streamable: progressive rendering as you generate.
Get started: Agent Development
3. Platform Builders (SDK Creators)
Build agent orchestration platforms, frameworks, or UI integrations.
Do you bring remote agents into your app?
Do you ship your agent into other apps you don't necessarily control?
Why A2UI:
- Standard protocol: interoperable with A2A and other transports.
- Extensible: custom component catalogs.
- Open source (Apache 2.0).
Get started: Community | Roadmap
When to Use A2UI
Use A2UI in the following scenarios:
- Agent-generated UI: Core purpose.
- Multi-agent systems: Standard protocol across trust boundaries.
- Cross-platform apps: One agent, many renderers (web/mobile/desktop).
- Security critical: Declarative data, no code execution.
- Brand consistency: Client controls styling.
Do not use A2UI for:
- Static websites: Use HTML/CSS.
- Simple text-only chat: Use Markdown.
- Remote widgets not integrated with client: Use iframes, like MCP Apps.
- Rapid UI + Agent app built together: Use AG-UI / CopilotKit.
See Agent UI Ecosystem for a detailed comparison.
Public Reference Agents (`docs/public/reference/agents.md`)
Agents (Server-Side)
Agents are server-side programs that generate A2UI messages in response to user requests.
The actual component rendering is done by the renderer,
after messages are transported to the client.
The agent is only responsible for generating the A2UI messages.
How Agents Work
The agent workflow typically involves the following steps:
- Receive user message.
- Process with LLM (Gemini, GPT, Claude, etc.).
- Generate A2UI JSON messages as structured output.
- Send to client via transport.
User interactions from the client can be treated as new user input.
Sample Agents
The A2UI repository includes sample agents you can learn from:
- Restaurant Finder
- Table reservations with forms.
- Written with the ADK.
- Rizzcharts
- A2UI Custom components demo.
- Written with the ADK.
- Orchestrator
- Passes A2UI messages from remote subagents.
- Written with the ADK.
Agent Types in A2A
1. User Facing Agent (standalone)
A user facing agent is one that is directly interacted with by the user.
2. User Facing Agent as a host for a Remote Agent
This is a pattern where the user facing agent is a host for one or more remote agents. The user facing agent will call the remote agent and the remote agent will generate the A2UI messages. This is a common pattern in A2A with the client agent calling the server agent.
In this pattern, the user-facing agent can handle messages in two ways:
- The user facing agent may "passthrough" the A2UI message without altering them.
- The user facing agent may alter the A2UI message before sending it to the client.
3. Remote Agent
A remote agent is not directly a part of the user facing UI. Instead it is registered in as a remote agent and can be called by the user facing agent. This is a common pattern in A2A with the client agent calling the server agent.
Public Reference Components (`docs/public/reference/components.md`)
Component Gallery
This page showcases all A2UI components with examples and usage patterns.
NOTE: Schema Files
=== "v0.8"
text[:material-code-json: Standard Catalog Definition (JSON Schema)](../../../specification/v0_8/json/standard_catalog_definition.json)=== "v0.9"
text[:material-code-json: Basic Catalog Definition (JSON Schema)](../../../specification/v0_9/catalogs/basic/catalog.json)
Layout Components
Row
Horizontal layout container. Children are arranged left-to-right.
=== "v0.8"
**Properties:** `children` (`explicitList` or `template`), `distribution`, `alignment`
```json
{
"id": "toolbar",
"component": {
"Row": {
"children": { "explicitList": ["btn1", "btn2", "btn3"] },
"distribution": "spaceBetween",
"alignment": "center"
}
}
}
```=== "v0.9"
**Properties:** `children` (array or template), `justify`, `align`
```json
{
"id": "toolbar",
"component": "Row",
"children": ["btn1", "btn2", "btn3"],
"justify": "spaceBetween",
"align": "center"
}
```Column
Vertical layout container. Children are arranged top-to-bottom.
=== "v0.8"
**Properties:** `children` (`explicitList` or `template`), `distribution`, `alignment`
```json
{
"id": "content",
"component": {
"Column": {
"children": { "explicitList": ["header", "body", "footer"] },
"distribution": "start",
"alignment": "stretch"
}
}
}
```=== "v0.9"
**Properties:** `children` (array or template), `justify`, `align`
```json
{
"id": "content",
"component": "Column",
"children": ["header", "body", "footer"],
"justify": "start",
"align": "stretch"
}
```List
Scrollable list of items. Supports static children and dynamic templates.
=== "v0.8"
**Properties:** `children` (`explicitList` or `template`), `direction`, `alignment`
```json
{
"id": "message-list",
"component": {
"List": {
"children": {
"template": {
"dataBinding": "/messages",
"componentId": "message-item"
}
},
"direction": "vertical"
}
}
}
```=== "v0.9"
**Properties:** `children` (array or template), `direction`, `align`
```json
{
"id": "message-list",
"component": "List",
"children": {
"componentId": "message-item",
"path": "/messages"
},
"direction": "vertical"
}
```Display Components
Text
Display text content with styling hints.
=== "v0.8"
**Properties:** `text` (BoundValue), `usageHint`
`usageHint` values: `h1`, `h2`, `h3`, `h4`, `h5`, `caption`, `body`
```json
{
"id": "title",
"component": {
"Text": {
"text": { "literalString": "Welcome to A2UI" },
"usageHint": "h1"
}
}
}
```=== "v0.9"
**Properties:** `text` (string or DataBinding), `variant`
`variant` values: `h1`, `h2`, `h3`, `h4`, `h5`, `caption`, `body`
```json
{
"id": "title",
"component": "Text",
"text": "Welcome to A2UI",
"variant": "h1"
}
```Image
Display images from URLs.
=== "v0.8"
**Properties:** `url` (BoundValue), `fit`, `usageHint`
```json
{
"id": "hero",
"component": {
"Image": {
"url": { "literalString": "https://example.com/hero.png" },
"fit": "cover",
"usageHint": "hero"
}
}
}
```=== "v0.9"
**Properties:** `url` (string or DataBinding), `fit`, `variant`
```json
{
"id": "hero",
"component": "Image",
"url": "https://example.com/hero.png",
"fit": "cover",
"variant": "hero"
}
```Icon
Display icons from the basic set defined in the catalog.
=== "v0.8"
**Properties:** `name` (BoundValue)
```json
{
"id": "check-icon",
"component": {
"Icon": {
"name": { "literalString": "check" }
}
}
}
```=== "v0.9"
**Properties:** `name` (string or DataBinding)
```json
{
"id": "check-icon",
"component": "Icon",
"name": "check"
}
```Divider
Visual separator line.
=== "v0.8"
**Properties:** `axis`
```json
{
"id": "separator",
"component": {
"Divider": {
"axis": "horizontal"
}
}
}
```=== "v0.9"
**Properties:** `axis`
```json
{
"id": "separator",
"component": "Divider",
"axis": "horizontal"
}
```Interactive Components
Button
Clickable button that triggers an action.
=== "v0.8"
**Properties:** `child` (component ID), `primary` (boolean), `action`
```json
{
"id": "submit-btn",
"component": {
"Button": {
"child": "submit-text",
"primary": true,
"action": {
"name": "submit_form"
}
}
}
}
```=== "v0.9"
**Properties:** `child` (component ID), `variant`, `action`
```json
{
"id": "submit-btn",
"component": "Button",
"child": "submit-text",
"variant": "primary",
"action": {
"event": {
"name": "submit_form"
}
}
}
```TextField
Text input field with optional validation.
=== "v0.8"
**Properties:** `label` (BoundValue), `text` (BoundValue), `textFieldType`, `validationRegexp`
`textFieldType` values: `shortText`, `longText`, `number`, `obscured`, `date`
```json
{
"id": "email-input",
"component": {
"TextField": {
"label": { "literalString": "Email Address" },
"text": { "path": "/user/email" },
"textFieldType": "shortText"
}
}
}
```=== "v0.9"
**Properties:** `label` (string), `value` (string or DataBinding), `textFieldType`, `validationRegexp`
`textFieldType` values: `shortText`, `longText`, `number`, `obscured`, `date`
```json
{
"id": "email-input",
"component": "TextField",
"label": "Email Address",
"value": { "path": "/user/email" },
"textFieldType": "shortText"
}
```CheckBox
Boolean toggle.
=== "v0.8"
**Properties:** `label` (BoundValue), `value` (BoundValue, boolean)
```json
{
"id": "terms-checkbox",
"component": {
"CheckBox": {
"label": { "literalString": "I agree to the terms" },
"value": { "path": "/form/agreedToTerms" }
}
}
}
```=== "v0.9"
**Properties:** `label` (string), `value` (DataBinding, boolean)
```json
{
"id": "terms-checkbox",
"component": "CheckBox",
"label": "I agree to the terms",
"value": { "path": "/form/agreedToTerms" }
}
```Slider
Numeric range input.
=== "v0.8"
**Properties:** `value` (BoundValue), `minValue`, `maxValue`
```json
{
"id": "volume",
"component": {
"Slider": {
"value": { "path": "/settings/volume" },
"minValue": 0,
"maxValue": 100
}
}
}
```=== "v0.9"
**Properties:** `value` (DataBinding), `minValue`, `maxValue`
```json
{
"id": "volume",
"component": "Slider",
"value": { "path": "/settings/volume" },
"minValue": 0,
"maxValue": 100
}
```DateTimeInput
Date and/or time picker.
=== "v0.8"
**Properties:** `value` (BoundValue), `enableDate`, `enableTime`
```json
{
"id": "date-picker",
"component": {
"DateTimeInput": {
"value": { "path": "/booking/date" },
"enableDate": true,
"enableTime": false
}
}
}
```=== "v0.9"
**Properties:** `value` (DataBinding), `enableDate`, `enableTime`
```json
{
"id": "date-picker",
"component": "DateTimeInput",
"value": { "path": "/booking/date" },
"enableDate": true,
"enableTime": false
}
```MultipleChoice (v0.8) / ChoicePicker (v0.9)
Select one or more options from a list.
=== "v0.8"
**Properties:** `options` (array), `selections` (BoundValue), `maxAllowedSelections`
```json
{
"id": "country-select",
"component": {
"MultipleChoice": {
"options": [
{ "label": { "literalString": "USA" }, "value": "us" },
{ "label": { "literalString": "Canada" }, "value": "ca" }
],
"selections": { "path": "/form/country" },
"maxAllowedSelections": 1
}
}
}
```=== "v0.9"
**Properties:** `options` (array), `selections` (DataBinding), `maxAllowedSelections`
```json
{
"id": "country-select",
"component": "ChoicePicker",
"options": [
{ "label": "USA", "value": "us" },
{ "label": "Canada", "value": "ca" }
],
"selections": { "path": "/form/country" },
"maxAllowedSelections": 1
}
```Container Components
Card
Container with elevation/border and padding.
=== "v0.8"
**Properties:** `child` (component ID)
```json
{
"id": "info-card",
"component": {
"Card": {
"child": "card-content"
}
}
}
```=== "v0.9"
**Properties:** `child` (component ID)
```json
{
"id": "info-card",
"component": "Card",
"child": "card-content"
}
```Modal
Overlay dialog triggered by an entry point component.
=== "v0.8"
**Properties:** `entryPointChild` (component ID), `contentChild` (component ID)
```json
{
"id": "confirmation-modal",
"component": {
"Modal": {
"entryPointChild": "open-modal-btn",
"contentChild": "modal-content"
}
}
}
```=== "v0.9"
**Properties:** `entryPointChild` (component ID), `contentChild` (component ID)
```json
{
"id": "confirmation-modal",
"component": "Modal",
"entryPointChild": "open-modal-btn",
"contentChild": "modal-content"
}
```Tabs
Tabbed interface for organizing content into switchable panels.
=== "v0.8"
**Properties:** `tabItems` (array of `{ title, child }`)
```json
{
"id": "settings-tabs",
"component": {
"Tabs": {
"tabItems": [
{ "title": { "literalString": "General" }, "child": "general-tab" },
{ "title": { "literalString": "Privacy" }, "child": "privacy-tab" }
]
}
}
}
```=== "v0.9"
**Properties:** `tabItems` (array of `{ title, child }`)
```json
{
"id": "settings-tabs",
"component": "Tabs",
"tabItems": [
{ "title": "General", "child": "general-tab" },
{ "title": "Privacy", "child": "privacy-tab" }
]
}
```Common Properties
All components share:
id(required): Unique identifier within the surface.accessibility: Accessibility attributes (label, role).weight: Flex-grow value when inside a Row or Column.
Version Differences Summary
The component names and properties are largely the same across versions. The structural differences are:
| Aspect | v0.8 | v0.9 |
|---|---|---|
| Component wrapper | "component": { "Text": { ... } } |
"component": "Text", ...props |
| String values | { "literalString": "Hello" } |
"Hello" |
| Children | { "explicitList": ["a", "b"] } |
["a", "b"] |
| Data binding | { "path": "/data" } |
{ "path": "/data" } (same) |
| Text/Image styling | usageHint |
variant |
| Button styling | primary: true |
variant: "primary" |
| Action format | { "name": "..." } |
{ "event": { "name": "..." } } |
| Choice component | MultipleChoice |
ChoicePicker |
| Layout alignment | distribution, alignment |
justify, align |
| TextField value | text |
value |
Live Examples
To see all components in action:
cd samples/client/angular
yarn start gallery[!NOTE]
Package Manager Usage: Running the built-in sample applications within the A2UI repository requires Yarn (yarn start gallery) as configured by Corepack workspaces. For your own regular usage and standalone projects outside this repository, use the package manager of your choice (e.g. npm, pnpm).
Further Reading
NOTE: Schema Files
=== "v0.8"
text[:material-code-json: Standard Catalog Definition (JSON Schema)](../../../specification/v0_8/json/standard_catalog_definition.json)=== "v0.9"
text[:material-code-json: Basic Catalog Definition (JSON Schema)](../../../specification/v0_9/catalogs/basic/catalog.json)
- Defining Your Own Catalog: Build your own components
- Theming Guide: Style components to match your brand
Public Reference Messages (`docs/public/reference/messages.md`)
Message Types
This reference provides detailed documentation for all A2UI message types.
Message Format
All A2UI messages are JSON objects sent as JSON Lines (JSONL). Each line contains exactly one message.
=== "v0.8 Message Types"
- `beginRendering` β Signal the client to render a surface
- `surfaceUpdate` β Add or update components
- `dataModelUpdate` β Update application state
- `deleteSurface` β Remove a surface=== "v0.9 Message Types"
- `createSurface` β Create a surface and specify its catalog
- `updateComponents` β Add or update components
- `updateDataModel` β Update application state
- `deleteSurface` β Remove a surface
All v0.9 messages include a `"version": "v0.9"` field.beginRendering (v0.8) / createSurface (v0.9)
Signals the client to initialize and render a surface.
=== "v0.8 β beginRendering"
### Schema
```typescript
{
beginRendering: {
surfaceId: string; // Required: Unique surface identifier
root: string; // Required: The ID of the root component to render
catalogId?: string; // Optional: URL of component catalog
styles?: object; // Optional: Styling information
}
}
```
### Properties
| Property | Type | Required | Description |
| ----------- | ------ | -------- | --------------------------------------------------------------------------------------- |
| `surfaceId` | string | β
| Unique identifier for this surface. |
| `root` | string | β
| The `id` of the component that should be the root of the UI tree for this surface. |
| `catalogId` | string | β | Identifier for the component catalog. Defaults to the v0.8 basic catalog if omitted. |
| `styles` | object | β | Styling information for the UI, as defined by the catalog. |
### Example
```json
{
"beginRendering": {
"surfaceId": "main",
"root": "root-component"
}
}
```
**With a custom catalog:**
```json
{
"beginRendering": {
"surfaceId": "custom-ui",
"root": "root-custom",
"catalogId": "https://my-company.com/a2ui/v0.8/my_custom_catalog.json"
}
}
```
Must be sent after component definitions. The client buffers `surfaceUpdate` and `dataModelUpdate` messages until `beginRendering` is received.=== "v0.9 β createSurface"
### Schema
```typescript
{
version: "v0.9";
createSurface: {
surfaceId: string; // Required: Unique surface identifier
catalogId: string; // Required: URL of component catalog
theme?: object; // Optional: Theme configuration
sendDataModel?: boolean; // Optional: Request client to send data model updates
}
}
```
### Properties
| Property | Type | Required | Description |
| --------------- | ------- | -------- | --------------------------------------------------------------- |
| `surfaceId` | string | β
| Unique identifier for this surface. |
| `catalogId` | string | β
| Identifier for the component catalog. |
| `theme` | object | β | Theme configuration (e.g., `primaryColor`). |
| `sendDataModel` | boolean | β | If true, client sends data model changes back to the server. |
### Example
```json
{
"version": "v0.9",
"createSurface": {
"surfaceId": "main",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
}
}
```
The root component is determined by convention: one component in `updateComponents` must have `"id": "root"`. The `catalogId` is required.surfaceUpdate (v0.8) / updateComponents (v0.9)
Add or update components within a surface.
=== "v0.8 β surfaceUpdate"
### Schema
```typescript
{
surfaceUpdate: {
surfaceId: string; // Required: Target surface
components: Array<{ // Required: List of components
id: string; // Required: Component ID
component: { // Required: Wrapper for component data
[ComponentType]: { // Required: Exactly one component type
...properties // Component-specific properties
}
}
}>
}
}
```
### Properties
| Property | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------ |
| `surfaceId` | string | β
| ID of the surface to update |
| `components` | array | β
| Array of component definitions |
### Component Object
Each object in the `components` array must have:
- `id` (string, required): Unique identifier within the surface
- `component` (object, required): A wrapper object that contains exactly one key, which is the component type (e.g., `Text`, `Button`).
### Examples
**Single component:**
```json
{
"surfaceUpdate": {
"surfaceId": "main",
"components": [
{
"id": "greeting",
"component": {
"Text": {
"text": {"literalString": "Hello, World!"},
"usageHint": "h1"
}
}
}
]
}
}
```
**Multiple components (adjacency list):**
```json
{
"surfaceUpdate": {
"surfaceId": "main",
"components": [
{
"id": "root",
"component": {
"Column": {
"children": {"explicitList": ["header", "body"]}
}
}
},
{
"id": "header",
"component": {
"Text": {
"text": {"literalString": "Welcome"}
}
}
},
{
"id": "body",
"component": {
"Card": {
"child": "content"
}
}
},
{
"id": "content",
"component": {
"Text": {
"text": {"path": "/message"}
}
}
}
]
}
}
```
**Updating existing component:**
```json
{
"surfaceUpdate": {
"surfaceId": "main",
"components": [
{
"id": "greeting",
"component": {
"Text": {
"text": {"literalString": "Hello, Alice!"},
"usageHint": "h1"
}
}
}
]
}
}
```
The component with `id: "greeting"` is updated (not duplicated).
### Usage Notes
Keep these usage notes in mind:
- One component must be designated as the `root` in the `beginRendering` message to serve as the tree root.
- Components form an adjacency list (flat structure with ID references).
- Sending a component with an existing ID updates that component.
- Children are referenced by ID.
- Components can be added incrementally (streaming).=== "v0.9 β updateComponents"
### Schema
```typescript
{
version: "v0.9";
updateComponents: {
surfaceId: string; // Required: Target surface
components: Array<{ // Required: List of components
id: string; // Required: Component ID
component: string; // Required: Component type name
...properties // Component-specific properties (flat)
}>
}
}
```
### Properties
| Property | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------ |
| `surfaceId` | string | β
| ID of the surface to update |
| `components` | array | β
| Array of component definitions |
### Component Object
The component structure is flat:
- `id` (string, required): Unique identifier within the surface
- `component` (string, required): Component type name (e.g., `"Text"`, `"Button"`)
- All other properties are top-level on the component object.
### Examples
**Single component:**
```json
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "main",
"components": [
{
"id": "greeting",
"component": "Text",
"text": "Hello, World!",
"variant": "h1"
}
]
}
}
```
**Multiple components:**
```json
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "main",
"components": [
{
"id": "root",
"component": "Column",
"children": ["header", "body"]
},
{
"id": "header",
"component": "Text",
"text": "Welcome"
},
{
"id": "body",
"component": "Card",
"child": "content"
},
{
"id": "content",
"component": "Text",
"text": {"path": "/message"}
}
]
}
}
```
**Updating existing component:**
```json
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "main",
"components": [
{
"id": "greeting",
"component": "Text",
"text": "Hello, Alice!",
"variant": "h1"
}
]
}
}
```
### Usage Notes
Keep these usage notes in mind:
- One component must have `"id": "root"` to serve as the tree root (convention, not a separate message field).
- Component type is a string (`"component": "Text"`) instead of a wrapper object.
- Properties are flat on the component object (no nesting under type key).
- Data binding uses `{"path": "/pointer"}` (JSON Pointer) β same key name as v0.8 but with standard JSON Pointer paths.
- Components can be added incrementally (streaming).Errors
| Error | Cause | Solution |
|---|---|---|
| Surface already exists | surfaceId is already in use |
Ensure surfaceId is globally unique for the renderer's lifetime. If using an orchestrator with subagents, the orchestrator is empowered to manage surface IDs as needed to avoid conflicts. |
| Surface not found | surfaceId does not exist |
Ensure surfaceId matches the created surface. In v0.8, surfaces are implicit, but v0.9+ requires createSurface. |
| Invalid component type | Unknown component type | Check component type exists in the negotiated catalog. |
| Invalid property | Property doesn't exist for this type | Verify against catalog schema. |
| Circular reference | Component references itself as a child | Fix component hierarchy. |
dataModelUpdate (v0.8) / updateDataModel (v0.9)
Update the data model that components bind to.
=== "v0.8 β dataModelUpdate"
### Schema
```typescript
{
dataModelUpdate: {
surfaceId: string; // Required: Target surface
path?: string; // Optional: Path to a location in the model
contents: Array<{ // Required: Data entries
key: string;
valueString?: string;
valueNumber?: number;
valueBoolean?: boolean;
valueMap?: Array<{...}>;
}>
}
}
```
### Properties
| Property | Type | Required | Description |
| ----------- | ------ | -------- | ---------------------------------------------------------------------------------------------------- |
| `surfaceId` | string | β
| ID of the surface to update. |
| `path` | string | β | Path to a location within the data model (e.g., `user`). If omitted, the update applies to the root. |
| `contents` | array | β
| An array of data entries as an adjacency list. Each entry has a `key` and a typed `value*` property. |
### The `contents` Adjacency List
The `contents` array is a list of key-value pairs. Each object in the array must have a `key` and exactly one `value*` property (`valueString`, `valueNumber`, `valueBoolean`, or `valueMap`). This structure is LLM-friendly and avoids issues with inferring types from a generic `value` field.
### Examples
**Initialize entire model:**
```json
{
"dataModelUpdate": {
"surfaceId": "main",
"contents": [
{
"key": "user",
"valueMap": [
{ "key": "name", "valueString": "Alice" },
{ "key": "email", "valueString": "[email protected]" }
]
},
{ "key": "items", "valueMap": [] }
]
}
}
```
**Update nested property:**
```json
{
"dataModelUpdate": {
"surfaceId": "main",
"path": "user",
"contents": [
{ "key": "email", "valueString": "[email protected]" }
]
}
}
```
This will change `/user/email` without affecting `/user/name`.
### Usage Notes
Keep these usage notes in mind:
- Data model is per-surface.
- Components automatically re-render when their bound data changes.
- Prefer granular updates to specific paths over replacing the entire model.
- Uses typed value fields (`valueString`, `valueNumber`, `valueBoolean`, `valueMap`) β LLM-friendly, no type inference needed.
- Any data transformation (e.g., formatting a date) must be done by the server before sending the message.=== "v0.9 β updateDataModel"
### Schema
```typescript
{
version: "v0.9";
updateDataModel: {
surfaceId: string; // Required: Target surface
path?: string; // Optional: JSON Pointer path (defaults to "/")
value?: any; // Optional: Value to set (omit to delete)
}
}
```
### Properties
| Property | Type | Required | Description |
| ----------- | ------ | -------- | ----------------------------------------------------- |
| `surfaceId` | string | β
| ID of the surface to update. |
| `path` | string | β | JSON Pointer path (e.g., `/user/email`). Defaults to `/` (root). |
| `value` | any | β | The value to set. If omitted, the key at `path` is removed. |
### Examples
**Initialize entire model:**
```json
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "main",
"path": "/",
"value": {
"user": {
"name": "Alice",
"email": "[email protected]"
},
"items": []
}
}
}
```
**Update nested property:**
```json
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "main",
"path": "/user/email",
"value": "[email protected]"
}
}
```
### Usage Notes
Keep these usage notes in mind:
- v0.9 uses standard JSON Pointer paths and plain JSON values β no typed wrappers.
- `path` defaults to `"/"` (root) if omitted.
- `value` can be any JSON type (string, number, boolean, object, array, null). Omit to delete.
- Simpler than v0.8's `contents` adjacency list β closer to standard JSON Patch semantics.
- Components referencing `{"path": "/user/email"}` auto-update when that path changes.deleteSurface
Remove a surface and all its components and data.
=== "v0.8 β deleteSurface"
### Schema
```typescript
{
deleteSurface: {
surfaceId: string; // Required: Surface to delete
}
}
```
### Example
```json
{
"deleteSurface": {
"surfaceId": "modal"
}
}
```=== "v0.9 β deleteSurface"
### Schema
```typescript
{
version: "v0.9";
deleteSurface: {
surfaceId: string; // Required: Surface to delete
}
}
```
### Example
```json
{
"version": "v0.9",
"deleteSurface": {
"surfaceId": "modal"
}
}
```Properties
| Property | Type | Required | Description |
|---|---|---|---|
surfaceId |
string | β | ID of the surface to delete |
Usage Notes
Keep these usage notes in mind:
- Removes all components associated with the surface.
- Clears the data model for the surface.
- Client should remove the surface from the UI.
- Safe to delete non-existent surface (no-op).
- Use when closing modals, dialogs, or navigating away.
- Identical structure in both versions (v0.9 just adds the
versionfield).
Message Ordering
Requirements
Message ordering must satisfy the following requirements:
beginRenderingmust come after the initialsurfaceUpdatemessages for that surface.surfaceUpdatecan come before or afterdataModelUpdate.- Messages for different surfaces are independent.
- Multiple messages can update the same surface incrementally.
Recommended Order
=== "v0.8"
```jsonl
{ "surfaceUpdate": { "surfaceId": "main", "components": [...] } }
{ "dataModelUpdate": { "surfaceId": "main", "contents": {...} } }
{ "beginRendering": { "surfaceId": "main", "root": "root-id" } }
```=== "v0.9"
```jsonl
{ "version": "v0.9", "createSurface": { "surfaceId": "main", "catalogId": "..." } }
{ "version": "v0.9", "updateComponents": { "surfaceId": "main", "components": [...] } }
{ "version": "v0.9", "updateDataModel": { "surfaceId": "main", "path": "/", "value": {...} } }
```Progressive Building
=== "v0.8"
```jsonl
{ "surfaceUpdate": { "surfaceId": "main", "components": [...] } } // Header
{ "surfaceUpdate": { "surfaceId": "main", "components": [...] } } // Body
{ "beginRendering": { "surfaceId": "main", "root": "root-id" } } // Render
{ "surfaceUpdate": { "surfaceId": "main", "components": [...] } } // Footer
{ "dataModelUpdate": { "surfaceId": "main", "contents": {...} } } // Data
```=== "v0.9"
```jsonl
{ "version": "v0.9", "createSurface": { "surfaceId": "main", "catalogId": "..." } }
{ "version": "v0.9", "updateComponents": { "surfaceId": "main", "components": [...] } } // Header
{ "version": "v0.9", "updateComponents": { "surfaceId": "main", "components": [...] } } // Body + Footer
{ "version": "v0.9", "updateDataModel": { "surfaceId": "main", "path": "/", "value": {...} } }
```Validation
=== "v0.8"
Validate against:
- **[server_to_client.json](../../../specification/v0_8/json/server_to_client.json)**: Message envelope schema.
- **[standard_catalog_definition.json](../../../specification/v0_8/json/standard_catalog_definition.json)**: Component schemas.=== "v0.9"
Validate against:
- **[server_to_client.json](../../../specification/v0_9/json/server_to_client.json)**: Message envelope schema.
- **[catalogs/basic/catalog.json](../../../specification/v0_9/catalogs/basic/catalog.json)**: Component schemas.Further Reading
- Component Gallery: All available component types
- Data Binding Guide: How data binding works
- Agent Development Guide: Generate valid messages
Public Reference Renderers (`docs/public/reference/renderers.md`)
Renderers (Client Libraries)
Renderers convert A2UI JSON messages into native UI components for different platforms.
The agents are responsible for generating the A2UI messages,
and the transports are responsible for delivering the messages to the client.
The client renderer library must buffer and handle A2UI messages, implement the A2UI lifecycle, render widgets, and route user actions back to the agent.
Let's use the web as an analogy. The A2UI protocol is like HTML. It provides a language and the semantics of the UI model. The agent is like the server that serves HTML to the client. The renderer is like a browser. It talks to the agent, interprets the A2UI protocol, and renders the UI. Just like there are multiple browser engines for HTML, there are multiple different renderers for A2UI.
You have a lot of flexibility, to bring custom components to a renderer, or build your own renderer to support your UI component framework.
Maintained Renderers
Web
| Renderer | Platform | v0.8 | v0.9.1 | v1.0 | Links |
|---|---|---|---|---|---|
| React | Web | β Stable | β Stable | π§ Planned | Code |
| Lit (Web Components) | Web | β Stable | β Stable | π§ Planned | Code |
| Angular | Web | β Stable | β Stable | π§ Planned | Code |
| Flutter (GenUI SDK) | Mobile/Desktop/Web | β Stable | β Stable | π§ Planned | Docs Β· Code |
Mobile
| Renderer | Platform | v0.8 | v0.9.1 | v1.0 | Links |
|---|---|---|---|---|---|
| Flutter (GenUI SDK) | Mobile/Desktop/Web | β Stable | β Stable | π§ Planned | Docs Β· Code |
| SwiftUI | iOS/macOS | β | β | π§ Planned | β |
| Jetpack Compose | Android | β | β Alpha | π§ Planned | Docs Β· Code |
Check the Roadmap for more.
Ecosystem Renderers
The community is building A2UI renderers for additional platforms:
- json-render β Vercel's React library for rendering A2UI catalogs via Zod schemas (comparison)
- A2UI-Android β Community Jetpack Compose renderer, 20+ components (~15 β, v0.8)
- a2ui-react-native β React Native renderer for iOS/Android (~9 β, v0.8)
- Lynx A2UI β ReactLynx renderer for A2UI (v0.9)
- AGenUI β Cross-platform native renderer covering iOS, Android, and HarmonyOS. (v0.9)
See the full ecosystem renderers list for more community projects and how to submit your own.
How Renderers Work
The rendering process typically involves the following steps:
- Receive A2UI messages from the transport.
- Parse the JSON and validate against the schema.
- Render using platform-native components.
- Style according to your app's theme.
Using a Renderer
Get started integrating A2UI into your application by following the setup guide for your chosen renderer:
Building a Renderer
Want to build a renderer for your platform?
- See the Roadmap for planned frameworks.
- Review existing renderers for patterns.
- Check out our Renderer Development Guide for details on implementing a renderer.
A compliant renderer must meet the following key requirements:
- Parse A2UI JSON messages, specifically the adjacency list format.
- Map A2UI components to native widgets.
- Handle data binding, lifecycle events.
- Process a sequence of incremental A2UI messages to build and update the UI.
- Support server-initiated updates.
- Support user actions.
For more information, see the following resources:
- Client Setup Guide: Integration instructions.
- Quickstart: Try the Lit renderer.
- Component Reference: What components to support.
Public Specification V0.8 A2a Extension (`docs/public/specification/v0.8-a2a-extension.md`)
A2UI Extension for A2A Protocol v0.8 β Legacy
NOTE: Living Document
This specification is automatically included from
specification/v0_8/docs/a2ui_extension_specification.md. Any updates to the specification will automatically appear here.
NOTE: Version Compatibility
This extension specification applies to A2UI v0.8 and the A2A Protocol. For the base A2UI protocol, see v0.8 Protocol Specification (Legacy).
For more information, see the following related documentation:
- A2UI Protocol v0.8 (Legacy).
- A2A Protocol Documentation.
--8<-- "specification/v0_8/docs/a2ui_extension_specification.md"
Public Specification V0.8 A2ui (`docs/public/specification/v0.8-a2ui.md`)
A2UI Protocol v0.8 β Legacy
Version 0.8 is a legacy release, maintained for backward compatibility. For production use, consider v0.9.1 (Current).
NOTE: Living Document
This specification is automatically included from
specification/v0_8/docs/a2ui_protocol.md. Any updates to the specification will automatically appear here.
For more information, see the following related documentation:
- v0.9.1 Protocol Specification (Current).
- v0.9 Protocol Specification (Stable).
- v1.0 Protocol Specification (Candidate).
- Evolution Guide: v0.8 β v0.9.
- A2A Extension Specification (Legacy).
--8<-- "specification/v0_8/docs/a2ui_protocol.md"
Public Specification V0.9 A2ui (`docs/public/specification/v0.9-a2ui.md`)
A2UI Protocol v0.9 β Stable
Version 0.9 is the stable protocol family. For production use, consider v0.9.1 (Current).
NOTE: Living Document
This specification is automatically included from
specification/v0_9/docs/a2ui_protocol.md. Any updates to the specification will automatically appear here.
For more information, see the following related documentation:
- v0.9.1 Protocol Specification (Current).
- v0.8 Protocol Specification (Legacy).
- v1.0 Protocol Specification (Candidate).
- Evolution Guide: v0.8 β v0.9.
--8<-- "specification/v0_9/docs/a2ui_protocol.md"
Public Specification V0.9 Evolution Guide (`docs/public/specification/v0.9-evolution-guide.md`)
Evolution Guide v0.8 β v0.9
This guide is automatically included from
specification/v0_9/docs/evolution_guide.md. Any updates will automatically appear here.
For more information, see the following related documentation:
- A2UI Protocol v0.8 (Legacy - what you're migrating from).
- A2UI Protocol v0.9 (Stable - what you're migrating to).
--8<-- "specification/v0_9/docs/evolution_guide.md"
Public Specification V0.9.1 A2ui Extension Specification (`docs/public/specification/v0.9.1-a2ui-extension-specification.md`)
A2UI Extension for A2A Protocol v0.9.1 β Current
NOTE: Living Document
This specification is automatically included from
specification/v0_9_1/docs/a2ui_extension_specification.md. Any updates to the specification will automatically appear here.
NOTE: Version Compatibility
This extension specification applies to A2UI v0.9.1 and the A2A Protocol. For the base A2UI protocol, see v0.9.1 Protocol Specification.
For more information, see the following related documentation:
- A2UI Protocol v0.9.1 (Current).
- A2A Protocol Documentation.
--8<-- "specification/v0_9_1/docs/a2ui_extension_specification.md"
Public Specification V0.9.1 A2ui (`docs/public/specification/v0.9.1-a2ui.md`)
A2UI Protocol v0.9.1 β Current
Version 0.9.1 is the current production release. For the legacy version, see v0.8 (Legacy).
NOTE: Living Document
This specification is automatically included from
specification/v0_9_1/docs/a2ui_protocol.md. Any updates to the specification will automatically appear here.
For more information, see the following related documentation:
- v0.9 Protocol Specification (Previous Stable).
- Evolution Guide: v0.9 β v0.9.1.
--8<-- "specification/v0_9_1/docs/a2ui_protocol.md"
Public Specification V0.9.1 Basic Catalog Implementation Guide (`docs/public/specification/v0.9.1-basic-catalog-implementation-guide.md`)
Basic Catalog Implementation Guide v0.9.1 β Current
NOTE: Living Document
This specification is automatically included from
specification/v0_9_1/docs/basic_catalog_implementation_guide.md. Any updates to the specification will automatically appear here.
--8<-- "specification/v0_9_1/docs/basic_catalog_implementation_guide.md"
Public Specification V0.9.1 Evolution Guide (`docs/public/specification/v0.9.1-evolution-guide.md`)
Evolution Guide v0.9 β v0.9.1
This guide is automatically included from
specification/v0_9_1/docs/evolution_guide.md. Any updates will automatically appear here.
For more information, see the following related documentation:
- A2UI Protocol v0.9 (Previous Stable - what you're migrating from).
- A2UI Protocol v0.9.1 (Current - what you're migrating to).
--8<-- "specification/v0_9_1/docs/evolution_guide.md"
Public Specification V1.0 A2ui Extension Specification (`docs/public/specification/v1.0-a2ui-extension-specification.md`)
A2UI Extension for A2A Protocol v1.0 β Candidate
NOTE: Living Document
This specification is automatically included from
specification/v1_0/extensions/a2a/docs/a2ui_extension_specification.md. Any updates to the specification will automatically appear here.
NOTE: Version Compatibility
This extension specification applies to A2UI v1.0 (previously known as v0.10 in draft) and the A2A Protocol. For the base A2UI protocol, see v1.0 Protocol Specification.
For more information, see the following related documentation:
- A2UI Protocol v1.0 (Candidate).
- A2A Protocol Documentation.
--8<-- "specification/v1_0/extensions/a2a/docs/a2ui_extension_specification.md"
Public Specification V1.0 A2ui (`docs/public/specification/v1.0-a2ui.md`)
A2UI Protocol v1.0 β Candidate
Version 1.0 (previously v0.10 in draft) is currently a release candidate. For production use, consider v0.9.1 (Current).
NOTE: Living Document
This specification is automatically included from
specification/v1_0/docs/a2ui_protocol.md. Any updates to the specification will automatically appear here.
For more information, see the following related documentation:
--8<-- "specification/v1_0/docs/a2ui_protocol.md"
Public Specification V1.0 Basic Catalog Implementation Guide (`docs/public/specification/v1.0-basic-catalog-implementation-guide.md`)
Basic Catalog Implementation Guide v1.0 β Candidate
NOTE: Living Document
This specification is automatically included from
catalogs/basic/v1/basic_catalog_implementation_guide.md. Any updates to the specification will automatically appear here.
--8<-- "catalogs/basic/v1/basic_catalog_implementation_guide.md"
Public Specification V1.0 Evolution Guide (`docs/public/specification/v1.0-evolution-guide.md`)
Evolution Guide v0.9.1 β v1.0
This guide is automatically included from
specification/v1_0/docs/evolution_guide.md. Any updates will automatically appear here.
For more information, see the following related documentation:
- A2UI Protocol v0.9.1 (Current - what you're migrating from).
- A2UI Protocol v1.0 (Candidate - what you're migrating to).
--8<-- "specification/v1_0/docs/evolution_guide.md"
Specification V0 8 Docs A2ui Extension Specification (`specification/v0_8/docs/a2ui_extension_specification.md`)
A2UI (Agent-to-Agent UI) Extension spec
Overview
This extension implements the A2UI (Agent-to-Agent UI) spec, a format for agents to send streaming, interactive user interfaces to clients.
Extension URI
The URI of this extension is https://a2ui.org/a2a-extension/a2ui/v0.8
This is the only URI accepted for this extension.
Core concepts
The A2UI extension is built on the following main concepts:
Surfaces: A "Surface" is a distinct, controllable region of the client's UI. The spec uses a surfaceId to direct updates to specific surfaces (e.g., a main content area, a side panel, or a new chat bubble). This allows a single agent stream to manage multiple UI areas independently.
Catalog Definition Document: The a2ui extension is component-agnostic. All UI components (e.g., Text, Row, Button) and their stylings are defined in a separate Catalog Definition Schema. This allows clients and servers to negotiate which catalog to use.
Schemas: The a2ui extension is defined by three primary JSON schemas:
Catalog Definition Schema: A standard format for defining a library of components and styles.
Server-to-Client Message Schema: The core wire format for messages sent from the agent to the client (e.g., surfaceUpdate, dataModelUpdate).
Client-to-Server Event Schema: The core wire format for messages sent from the client to the agent (e.g., userAction).
Client Capabilities: The client sends its capabilities to the server in an a2uiClientCapabilities object. This object is included in the metadata field of every A2A Message sent from the client to the server. This object allows the client to declare which catalogs it supports.
Agent Card details
Agents advertise their A2UI capabilities in their AgentCard within the AgentCapabilities.extensions list. The params object defines the agent's specific UI support.
Example AgentExtension block:
{
"uri": "https://a2ui.org/a2a-extension/a2ui/v0.8",
"description": "Ability to render A2UI",
"required": false,
"params": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_8/standard_catalog_definition.json",
"https://my-company.com/a2ui/v0.8/my_custom_catalog.json"
],
"acceptsInlineCatalogs": true
}
}Parameter definitions
params.supportedCatalogIds: (OPTIONAL) An array of strings, where each string is a URI pointing to a component Catalog Definition Schema that the agent can generate.params.acceptsInlineCatalogs: (OPTIONAL) A boolean indicating if the agent can accept aninlineCatalogsarray in the client'sa2uiClientCapabilities. If omitted, this defaults tofalse.
Extension activation
Clients indicate their desire to use the A2UI extension by specifying it via the transport-defined A2A extension activation mechanism.
For JSON-RPC and HTTP transports, this is indicated via the X-A2A-Extensions HTTP header.
For gRPC, this is indicated via the X-A2A-Extensions metadata value.
Activating this extension implies that the server can send A2UI-specific messages (like surfaceUpdate) and the client is expected to send A2UI-specific events (like userAction).
Data encoding
A2UI messages are encoded as an A2A DataPart.
To identify a DataPart as containing A2UI data, it must have the following metadata:
mimeType:application/json+a2ui
The data field of the DataPart contains the A2UI JSON message (e.g., surfaceUpdate, userAction).
Example A2UI DataPart:
{
"data": {
"beginRendering": {
"surfaceId": "outlier_stores_map_surface"
}
},
"kind": "data",
"metadata": {
"mimeType": "application/json+a2ui"
}
}Specification V0 8 Docs A2ui Protocol (`specification/v0_8/docs/a2ui_protocol.md`)
<h1>A2UI (Agent to UI) Protocol</h1>A Specification for a JSONL-Based, Streaming UI Protocol
Created: Sep 19, 2025
Updated: Nov 12, 2025
Design requirements
The A2UI (Agent to UI) protocol should be a system where an LLM can stream a platform-agnostic, abstract UI definition to a client, which then renders it progressively using a native widget set. Every major design choice is traced back to the core challenges of LLM generation, perceived performance, and platform independence.
Requirement: The protocol must be easily generated by a Transformer Large Language Model (LLM)
This is the most critical driver. This requirement directly leads to several design choices:
Declarative, Simple Structure: The protocol should use a straightforward, declarative format ("this is a column with these children") rather than an imperative one ("now, add a column; then, append a text widget to it"). LLMs excel at generating structured, declarative data.
Flat Component List (Adjacency List): Requiring an LLM to generate a perfectly nested JSON tree in a single pass is difficult and error-prone. A flat list of components, where relationships are defined by simple string IDs, is much easier to generate piece by piece. The model can "think" of a component, give it an ID, and then reference that ID later without worrying about tree depth or object nesting.
Stateless Messages: Each JSONL message is a self-contained unit of information (componentUpdate, dataModelUpdate). This is ideal for a streaming LLM, which can output these messages incrementally as it processes a request.
Requirement: The UI must render progressively for a fast, responsive user experience
The system must feel fast to the user, even if the full UI is complex and takes time to generate.
Streaming via JSONL/SSE: This is a direct solution. The client doesn't have to wait for a single, massive JSON payload. It can start receiving and processing UI components immediately, improving perceived performance.
Requirement: The protocol must be platform-agnostic
The same server-side logic should be able to render a UI on a Flutter app, a web browser, or potentially other platforms without modification.
Client-Defined widget catalog: This is the core of the platform-agnostic design. The protocol should define an abstract component tree (e.g., "I need a Card with a Row inside"). It is the client's responsibility to map these abstract types to its native widget implementations (a Flutter Card widget, an HTML `` with card styling, etc.). The server only needs to know the names of the components the client supports.
Requirement: State management must be efficient and decoupled from the UI structure
Changing a piece of text in the UI should not require resending the entire UI definition.
Separation of Data and Components: Having distinct componentUpdate and data model update messages is key. The UI structure can be sent once, and subsequent updates can be small dataModelUpdate messages that only contain the changed data.
Requirement: The communication architecture must be robust and scalable
The system needs a clear, reliable way to handle both server-pushed UI and client-initiated events.
Unidirectional UI Stream: Using a one-way stream (SSE) for UI updates simplifies the client's logic. It only needs to listen and react. This is a more robust pattern for server-push than trying to manage a complex bidirectional channel.
Event Handling: Event handling is done via an A2A message from the client to the server agent
Introduction
The A2UI Protocol is a protocol designed for rendering user interfaces from a stream of JSON objects sent from a server. Its core philosophy emphasizes a clean separation of UI structure and application data, enabling progressive rendering as the client processes each message.
The protocol is designed to be "LLM-friendly," meaning its structure is declarative and straightforward, making it easy for a generative model to produce. A core feature of A2UI is its extensible component model. The set of available UI components is not fixed by the protocol but is defined in a separate Catalog, allowing for platform-specific or custom components.
Communication occurs via a JSON Lines (JSONL) stream. The client parses each line as a distinct message and incrementally builds the UI. The server-to-client protocol defines four message types:
surfaceUpdate: Provides a list of component definitions to be added to or updated in a specific UI area called a "surface."dataModelUpdate: Provides new data to be inserted into or to replace a surface's data model. Each surface has its own data model.beginRendering: Signals to the client that it has enough information to perform the initial render, specifying the ID of the root component and, optionally, the component catalog to use.deleteSurface: Explicitly removes a surface and its contents from the UI.
Client-to-server communication for user interactions is handled separately via an A2A message. This message can be one of two types:
userAction: Reports a user-initiated action from a component.error: Reports a client-side error.
This keeps the primary data stream unidirectional.
Section 1: Foundational architecture and data flow
This document specifies the architecture and data formats for the A2UI protocol. The design is guided by principles of strict separation of concerns, versioning, and progressive rendering.
1.1. Core philosophy: decoupling and contracts
The central philosophy of A2UI is the decoupling of three key elements:
- The Component Tree (The Structure): A server-provided tree of abstract components that describes the UI's structure. This is defined by
surfaceUpdatemessages. - The Data Model (The State): A server-provided JSON object containing the dynamic values that populate the UI, such as text, booleans, or lists. This is managed via
dataModelUpdatemessages. - The Widget Registry (The "Catalog"): A client-defined mapping of component types (e.g., "Row", "Text") to concrete, native widget implementations. This registry is part of the client application, not the protocol stream. The server must generate components that the target client's registry understands.
1.2. The JSONL stream: The unit of communication
All UI descriptions are transmitted from the server to the client as a stream of JSON objects, formatted as JSON Lines (JSONL). Each line is a separate, compact JSON object representing a single message. This allows the client to parse and process each part of the UI definition as it arrives, enabling progressive rendering.
1.3. Surfaces: Managing Multiple UI Regions
A Surface is a contiguous portion of screen real estate into which a A2UI UI can be rendered. The protocol introduces the concept of a surfaceId to uniquely identify and manage these areas. This allows a single A2UI stream to control multiple, independent UI regions simultaneously. Each surface has a separate root component and a separate hierarchy of components. Each surface has a separate data model, to avoid collision of keys when working with a large number of surfaces.
For example, in a chat application, each AI-generated response could be rendered into a separate surface within the conversation history. A separate, persistent surface could be used for a side panel that displays related information.
The surfaceId is a property within each server-to-client message that directs changes to the correct area. It is used with messages like beginRendering, surfaceUpdate, dataModelUpdate, and deleteSurface to target a specific surface.
1.4. Data Flow Model
The A2UI protocol is composed of a server-to-client stream describing UI and individual events sent to the server. The client consumes the stream, builds the UI, and renders it. Communication occurs via a JSON Lines (JSONL) stream, typically transported over Server-Sent Events (SSE).
Server Stream: The server begins sending the JSONL stream over an SSE connection.
Client-Side Buffering: The client receives messages and buffers them:
surfaceUpdate: Component definitions are stored in aMap<String, Component>, organized bysurfaceId. If a surface doesn't exist, it is created.dataModelUpdate: The client's internal JSON data model is built or updated.
Render Signal: The server sends a
beginRenderingmessage with therootcomponent's ID. This prevents a "flash of incomplete content." The client buffers incoming components and data but waits for this explicit signal before attempting the first render, ensuring the initial view is coherent.Client-Side Rendering: The client, now in a "ready" state, starts at the
rootcomponent. It recursively walks the component tree by looking up component IDs in its buffer. It resolves any data bindings against the data model and uses itsWidgetRegistryto instantiate native widgets.User Interaction and Event Handling: The user interacts with a rendered widget (e.g., taps a button). The client constructs a
userActionJSON payload, resolving any data bindings from the component'saction.context. It sends this payload to the server via an A2A message.Dynamic Updates: The server processes the
userAction. If the UI needs to change in response, the server sends newsurfaceUpdateanddataModelUpdatemessages over the original SSE stream. As these arrive, the client updates its component buffer and data model, and the UI re-renders to reflect the changes. The server can also senddeleteSurfaceto remove a UI region.
sequenceDiagram
participant Server
participant Client
Server->>+Client: SSE Connection (JSONL Stream)
Client->>Client: 1. Parse JSONL message
loop Until 'beginRendering'
Client->>Client: 2a. Process surfaceUpdate
Client->>Client: 2b. Process dataModelUpdate
end
Client->>Client: 3. Process beginRendering (rootId: 'root', isReady: true)
Note right of Client: 4. Triggers UI build for a surface
Client->>Client: 5. Build widget tree from 'root'
Client->>Client: 6. Resolve data bindings
Client->>Client: 7. Look up widgets in WidgetRegistry
Client-->>-Server: (UI is rendered)
Note over Client: 8. User interacts with UI (e.g., clicks button)
Client->>Client: 9. Construct userAction payload
Client->>+Server: 10. A2A Message sent to server (Client Event JSON with userAction)
Server-->>-Client: 11. OK
loop Dynamic Updates in Response to Event
Server->>+Client: surfaceUpdate, dataModelUpdate, or deleteSurface (via SSE)
Client->>Client: Update component map, data model, or remove surface
Note right of Client: Triggers UI rebuild
Client-->>-Server: (UI is updated)
end1.5. Full Stream Example
The following is a complete, minimal example of a JSONL stream that renders a user profile card.
{"surfaceUpdate": {"components": [{"id": "root", "component": {"Column": {"children": {"explicitList": ["profile_card"]}}}}]}}
{"surfaceUpdate": {"components": [{"id": "profile_card", "component": {"Card": {"child": "card_content"}}}]}}
{"surfaceUpdate": {"components": [{"id": "card_content", "component": {"Column": {"children": {"explicitList": ["header_row", "bio_text"]}}}}]}}
{"surfaceUpdate": {"components": [{"id": "header_row", "component": {"Row": {"alignment": "center", "children": {"explicitList": ["avatar", "name_column"]}}}}]}}
{"surfaceUpdate": {"components": [{"id": "avatar", "component": {"Image": {"url": {"literalString": "https://www.example.com/profile.jpg"}}}}]}}
{"surfaceUpdate": {"components": [{"id": "name_column", "component": {"Column": {"alignment": "start", "children": {"explicitList": ["name_text", "handle_text"]}}}}]}}
{"surfaceUpdate": {"components": [{"id": "name_text", "component": {"Text": {"usageHint": "h3", "text": {"literalString": "A2A Fan"}}}}]}}
{"surfaceUpdate": {"components": [{"id": "handle_text", "component": {"Text": {"text": {"literalString": "@a2a_fan"}}}}]}}
{"surfaceUpdate": {"components": [{"id": "bio_text", "component": {"Text": {"text": {"literalString": "Building beautiful apps from a single codebase."}}}}]}}
{"dataModelUpdate": {"contents": {}}}
{"beginRendering": {"root": "root"}}Section 2: The Component Model
A2UI's component model is designed for flexibility, separating the protocol from the component set.
2.1. Catalog Negotiation
A Catalog defines the contract between the server and the client for the UI that can be rendered. It contains a list of supported component types (e.g., Row, Text), their properties, and available styles. A catalog is defined by a Catalog Definition Document.
There is a Standard Catalog associated with each version of the A2UI protocol. For v0.8, its identifier is https://a2ui.org/specification/v0_8/standard_catalog_definition.json.
Catalog IDs are simple string identifiers. While they can be anything, it is conventional to use a URI within a domain that you own, to simplify debugging, avoid confusion, and avoid name collisions. Furthermore, if any changes are made to a catalog that could break compatibility between an agent and renderer, a new catalogId must be assigned. This ensures clear versioning and prevents unexpected behavior if an agent has changes but the client does not, or vice versa.
The negotiation process allows the client and server to agree on which catalog to use for a given UI surface. This process is designed to be flexible, supporting standard, custom, and even dynamically-defined catalogs.
The flow is as follows:
1. Server Advertises Capabilities
The server (agent) advertises its capabilities in its Agent Card as part of the A2A protocol. For A2UI, this includes which catalogs it supports and whether it can handle catalogs defined inline by the client.
supportedCatalogIds(array of strings, optional): A list of IDs for all pre-defined catalogs the agent is known to support.acceptsInlineCatalogs(boolean, optional): Iftrue, the server can processinlineCatalogssent by the client. Defaults tofalse.
Example Server Agent Card Snippet:
{
"name": "Restaurant Finder",
"capabilities": {
"extensions": [
{
"uri": "https://a2ui.org/a2a-extension/a2ui/v0.8",
"params": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_8/standard_catalog_definition.json",
"https://my-company.com/a2ui/v0.8/my_custom_catalog.json"
],
"acceptsInlineCatalogs": true
}
}
]
}
}Note that this is not a strict contract and purely included as a signal to help orchestrators and clients identify agents with matching UI capabilities. At runtime, orchestrating agents may dynamically delegate tasks to subagents which support additional catalogs that the orchestrating agent did not advertise. Thus, clients should consider the advertised supportedCatalogIds as a subset of the true catalogs that the agent or its subagents may support.
2. Client Declares Supported Catalogs
In every message sent to the server, the client includes an a2uiClientCapabilities object within the A2A Message's metadata field. This object informs the agent server of all catalogs the client can render.
supportedCatalogIds(array of strings, required): A list of identifiers for all pre-defined catalogs the client supports. The client must explicitly include the standard catalog ID here if it supports the standard catalog. The contents of these catalogs are expected to be compiled into the agent server and not downloaded at runtime, in order to prevent malicious content being injected into the prompt dynamically, and ensure predictable results.inlineCatalogs(array of objects, optional): An array of full Catalog Definition Documents. This allows a client to provide custom, on-the-fly catalogs, typically for use in local development workflows where it is faster to update a catalog in one place on the client. This may only be provided if the server has advertisedacceptsInlineCatalogs: true.
Example A2A Message with Client Capabilities:
{
"metadata": {
"a2uiClientCapabilities": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_8/standard_catalog_definition.json",
"https://my-company.com/a2ui_catalogs/custom-reporting-catalog-1.2"
],
"inlineCatalogs": [
{
"catalogId": "https://my-company.com/inline_catalogs/temp-signature-pad-catalog",
"components": {
"SignaturePad": {
"type": "object",
"properties": {"penColor": {"type": "string"}}
}
},
"styles": {}
}
]
}
},
"message": {
"prompt": {
"text": "Find me a good restaurant"
}
}
}3. Server Chooses Catalog and Renders
The server receives the client's capabilities and chooses a catalog to use for a specific UI surface. The server specifies its choice in the beginRendering message using the catalogId field.
catalogId(string, optional): The identifier of the chosen catalog. This ID must be one of thesupportedCatalogIdsor thecatalogIdfrom one of theinlineCatalogsprovided by the client.
If the catalogId is omitted, the client MUST default to the standard catalog for the protocol version (https://a2ui.org/specification/v0_8/standard_catalog_definition.json).
Example beginRendering Message:
{
"beginRendering": {
"surfaceId": "unique-surface-1",
"catalogId": "https://my-company.com/inline_catalogs/temp-signature-pad-catalog",
"root": "root-component-id"
}
}Each surface can use a different catalog, providing a high degree of flexibility, particularly in multi-agent systems where different agents may support different catalogs.
Schemas for Developers
When building an agent, it is recommended to use a resolved schema that includes the specific component catalog you are targeting (e.g., a custom schema combining server_to_client.json with your https://my-company.com/a2ui_catalogs/custom-reporting-catalog-1.2 definition). This provides the LLM with a strict definition of all available components and their properties, as well as the catalog-specific styles, leading to more reliable UI generation. The generic server_to_client.json is the abstract wire protocol, while the resolved schema is the concrete tool for generation.
In order to do the substitution, based on the standard server_to_client_schema and a custom_catalog_definition object, you can use JSON manipulation logic similar to:
component_properties = custom_catalog_definition["components"]
style_properties = custom_catalog_definition["styles"]
resolved_schema = copy.deepcopy(server_to_client_schema)
resolved_schema["properties"]["surfaceUpdate"]["properties"]["components"]["items"]["properties"]["component"]["properties"] = component_properties
resolved_schema["properties"]["beginRendering"]["properties"]["styles"]["properties"] = style_propertiesSee server_to_client_with_standard_catalog.json for an example of a resolved
schema which has the components substituted in.
2.2. The `surfaceUpdate` Message
This message is the primary way UI structure is defined. It contains a surfaceId and a components array.
{
"surfaceUpdate": {
"surfaceId": "main_content_area",
"components": [
{
"id": "unique-component-id",
"component": {
"Text": {
"text": { "literalString": "Hello, World!" }
}
}
},
{
"id": "another-component-id",
"component": { ... }
}
]
}
}components: A required flat list of component instances.
2.3. The Component Object
Each object in the components array has the following structure:
id: A required, unique string that identifies this specific component instance. This is used for parent-child references.component: A required object that defines the component's type and properties.
2.4.`component` (Generic Object)
On the wire, this object is generic. Its structure is not defined by the core A2UI protocol. Instead, its validation is based on the active Catalog. It is a wrapper object that must contain exactly one key, where the key is the string name of the component type from the catalog (e.g., "Text", "Row"). The value is an object containing the properties for that component, as defined in the catalog.
Example: A Text component:
"component": {
"Text": {
"text": { "literalString": "This is text" }
}
}A Button component:
"component": {
"Button": {
"label": { "literalString": "Click Me" },
"action": { "name": "submit_form" }
}
}The full set of available component types and their properties is defined by a Catalog Schema, not in the core protocol schema.
Section 3: UI Composition
3.1. The Adjacency List Model
The A2UI protocol defines the UI as a flat list of components. The tree structure is built implicitly using ID references. This is known as an adjacency list model.
Container components (like Row, Column, List, Card) have properties that reference the id of their child component(s). The client is responsible for storing all components in a map (e.g., Map<String, Component>) and recreating the tree structure at render time.
This model allows the server to send component definitions in any order, as long as all necessary components are present by the time beginRendering is sent.
flowchart TD
subgraph "Server Stream (JSONL)"
A("<b>surfaceUpdate</b><br>components: [root, title, button, button_text]")
B("<b>beginRendering</b><br>root: 'root'")
end
subgraph "Client-Side Buffer (Map)"
C("root: {id: 'root', type: Column, children: ['title', 'button']}")
D("title: {id: 'title', type: Text, text: 'Welcome'}")
E("button: {id: 'button', type: Button, child: 'button_text'}")
E_TEXT("button_text: {id: 'button_text', type: Text, text: 'Go'}")
end
subgraph "Rendered Widget Tree"
F(Column) --> G(Text: 'Welcome')
F --> H(Button)
H --> I(Text: 'Go')
end
A -- "Parsed and stored" --> C
A -- "Parsed and stored" --> D
A -- "Parsed and stored" --> E
A -- "Parsed and stored" --> E_TEXT
B -- "Triggers build from buffer" --> F3.2. Container Children: `explicitList` vs. `template`
Container components (Row, Column, List) define their children using a children object, which must contain either explicitList or template.
explicitList: An array of componentidstrings. This is used for static, known children.template: An object used to render a dynamic list of children from a data-bound list.
{
"type": "object",
"description": "Defines the children of a container component. Must contain exactly one of `explicitList` or `template`.",
"properties": {
"explicitList": {
"type": "array",
"description": "An ordered list of component IDs that are direct children.",
"items": {
"type": "string",
"description": "The ID of a child component."
}
},
"template": {
"type": "object",
"description": "Defines a template for rendering dynamic lists of children.",
"properties": {
"dataBinding": {"$ref": "#/definitions/DataPath"},
"componentId": {
"type": "string",
"description": "The ID of the component to use as a template for each item in the data-bound list."
}
},
"required": ["dataBinding", "componentId"],
"additionalProperties": false
}
},
"minProperties": 1,
"maxProperties": 1
}3.3. Dynamic List Rendering with `template`
To render dynamic lists, a container uses the template property.
dataBinding: A path to a list in the data model (e.g.,/user/posts).componentId: Theidof another component in the buffer to use as a template for each item in the list.
The client will iterate over the list at dataBinding and, for each item, render the component specified by componentId. The item's data is made available to the template component for relative data binding.
Section 4: Dynamic Data & State Management
A2UI enforces a clean separation between the UI's structure (components) and its dynamic data (data model).
4.1. The `dataModelUpdate` Message
This message is the only way to modify the client's data model.
surfaceId: The unique identifier for the UI surface this data model update applies to.path: An optional path to a location within the data model (e.g., '/user/name'). If omitted, the update applies to the root of the data model.contents: An array of data entries arranged as an adjacency list. Each entry must contain a 'key' and exactly one corresponding typed 'value*' property (e.g.valueString,valueNumber,valueBoolean,valueMap).valueMap: A JSON object representing a map as an adjacency list.
Example: Updating the data model
{
"dataModelUpdate": {
"surfaceId": "main_content_area",
"path": "user",
"contents": [
{"key": "name", "valueString": "Bob"},
{"key": "isVerified", "valueBoolean": true},
{
"key": "address",
"valueMap": [
{"key": "street", "valueString": "123 Main St"},
{"key": "city", "valueString": "Anytown"}
]
}
]
}
}4.2. Data Binding (The `BoundValue` Object)
Components connect to the data model through binding. Any property that can be data-bound (like text on a Text component) accepts a BoundValue object. This object defines either a literal value, a data path, or both as a shorthand for initialization.
From the catalog schema, a bound text property looks like this:
{
"type": "object",
"description": "A value that can be either a literal string or bound to the data model.",
"properties": {
"literalString": {
"type": "string",
"description": "A static string value."
},
"path": {"$ref": "#/definitions/DataPath"}
},
"minProperties": 1,
"additionalProperties": false
}A component can also bind to numbers (literalNumber), booleans (literalBoolean), or arrays (literalArray). The behavior depends on which properties are provided:
Literal Value Only: If only a
literal*value (e.g.,literalString) is provided, the value is static and displayed directly.json"text": { "literalString": "Hello" }Path Only: If only
pathis provided, the value is dynamic. It's resolved from the data model at render time.json"text": { "path": "/user/name" }Path and Literal Value (Initialization Shorthand): If both
pathand aliteral*value are provided, it serves as a shorthand for data model initialization. The client MUST:- Update the data model at the specified
pathwith the providedliteral*value. This is an implicitdataModelUpdate. - Bind the component property to that
pathfor rendering and future updates.
This allows the server to set a default value and bind to it in a single step.
json// This initializes data model at '/user/name' to "Guest" and binds to it. "text": { "path": "/user/name", "literalString": "Guest" }- Update the data model at the specified
The client's interpreter is responsible for resolving these paths against the data model before rendering. The A2UI protocol supports direct 1:1 binding; it does not include transformers (e.g., formatters, conditionals). Any data transformation must be performed by the server before sending it in a dataModelUpdate.
Section 5: Event Handling
While the server-to-client UI definition is a one-way stream (e.g., over SSE), user interactions are communicated back to the server using an A2A message.
5.1. The Client Event Message
The client sends a single JSON object that acts as a wrapper. It must contain exactly one of the following keys: userAction or error.
5.2. The `userAction` Message
This message is sent when the user interacts with a component that has an action defined. It is the primary mechanism for user-driven events.
The userAction object has the following structure:
name(string, required): The name of the action, taken directly from theaction.nameproperty of the component (e.g., "submit_form").surfaceId(string, required): Theidof the surface where the event originated.sourceComponentId(string, required): Theidof the component that triggered the event (e.g., "my_button").timestamp(string, required): An ISO 8601 timestamp of when the event occurred (e.g., "2025-09-19T17:01:00Z").context(object, required): A JSON object containing the key-value pairs from the component'saction.context, after resolving allBoundValues against the data model.
The process for resolving the action.context remains the same: the client iterates over the context array, resolves all literal or data-bound values, and constructs the context object.
5.3. The `error` Message
This message provides a feedback mechanism for the server. It is sent when the client encounters an error, for instance, during UI rendering or data binding. The content of the object is flexible and can contain any relevant error information.
5.4. Event Flow Example (`userAction`)
Component Definition (from
surfaceUpdate):json{ "surfaceUpdate": { "surfaceId": "main_content_area", "components": [ { "id": "submit_btn_text", "component": { "Text": { "text": {"literalString": "Submit"} } } }, { "id": "submit_btn", "component": { "Button": { "child": "submit_btn_text", "action": { "name": "submit_form", "context": [ { "key": "userInput", "value": {"path": "/form/textField"} }, {"key": "formId", "value": {"literalString": "f-123"}} ] } } } } ] } }Data Model (from
dataModelUpdate):json{ "dataModelUpdate": { "surfaceId": "main_content_area", "path": "form", "contents": [{"key": "textField", "valueString": "User input text"}] } }User Action: The user taps the "submit_btn" button.
Client-Side Resolution: The client resolves the
action.context.Client-to-Server Request: The client sends a
POSTrequest tohttps://api.example.com/handle_eventwith the following JSON body:json{ "userAction": { "name": "submit_form", "surfaceId": "main_content_area", "sourceComponentId": "submit_btn", "timestamp": "2025-09-19T17:05:00Z", "context": { "userInput": "User input text", "formId": "f-123" } } }Server Response: The server processes this event. If the UI needs to change as a result, the server sends new
surfaceUpdateordataModelUpdatemessages over the separate SSE stream.
Section 6: Client-Side Implementation
A robust client-side interpreter for A2UI should be composed of several key components:
- JSONL Parser: A parser capable of reading the stream line by line and decoding each line as a separate JSON object.
- Message Dispatcher: A mechanism (e.g., a
switchstatement) to identify the message type (beginRendering,surfaceUpdate, etc.) and route it to the correct handler. - Component Buffer: A
Map<String, Component>that stores all component instances by theirid. This is populated bycomponentUpdatemessages. - Data Model Store: A
Map<String, dynamic>(or similar) that holds the application state. This is built and modified bydataModelUpdatemessages. - Interpreter State: A state machine to track if the client is ready to render (e.g., a
_isReadyToRenderboolean that is set totruebybeginRendering). - Widget Registry: A developer-provided map (e.g.,
Map<String, WidgetBuilder>) that associates component type strings ("Row", "Text") with functions that build native widgets. - Binding Resolver: A utility that can take a
BoundValue(e.g.,{ "path": "/user/name" }) and resolve it against the Data Model Store. - Surface Manager: Logic to create, update, and delete UI surfaces based on
surfaceId. - Event Handler: A function, exposed to the
WidgetRegistry, that constructs and sends the client event message (e.g.,userAction) to the configured REST API endpoint.
Section 7: Complete A2UI Server To Client JSON Schema
This section provides the formal JSON Schema for a single server-to-client message in the A2UI JSONL stream. Each line in the stream must be a valid JSON object that conforms to this schema. It includes the entire base catalog of components, but the components may be swapped out for other components supported by the client. It is optimized to be able to be generated in structured output mode from various LLMs.
{% include "../specification/json/server_to_client_with_standard_catalog.json" %}Section 8: Complete A2UI Client to Server JSON Schema
This section provides the formal JSON Schema for a single client-to-server message in the A2UI protocol.
{% include "../specification/json/client_to_server.json" %}Specification V0 8 Docs Custom Catalog Changes (`specification/v0_8/docs/custom_catalog_changes.md`)
Summary of Custom Catalog negotiation changes in A2UI v0.8
This document summarizes the changes made to the A2UI protocol in v0.8 to support a more flexible and powerful custom catalog negotiation mechanism. It is intended as a guide for developers implementing these changes in agent or renderer libraries.
The previous mechanism, which involved a single, one-time clientUiCapabilities message, has been deprecated. The new approach allows for a more dynamic, per-request declaration of capabilities, enabling a single client to support multiple catalogs and allowing the agent to choose the most appropriate one for each UI surface.
Key changes to the protocol
Agent capability advertisement (
supportedCatalogIds,acceptsInlineCatalogs): The agent's role in negotiation has been expanded. It now can declare a list of supported catalog IDs, in addition to whether it is capable of processing catalogs defined "inline" by the client.- Relevant Doc:
a2ui_extension_specification.md
- Relevant Doc:
Client capabilities via A2A metadata: The client now sends its capabilities in an
a2uiClientCapabilitiesobject. Crucially, this is no longer a standalone message but is included in themetadatafield of the A2AMessagefor every A2AMessagesent to the agent.- This object contains
supportedCatalogIds(an array of known catalog IDs) and an optionalinlineCatalogs(an array of full catalog definitions). - Relevant doc: The new process is explained in the
a2ui_protocol.mdsection on Catalog Negotiation. - Relevant schema:
a2ui_client_capabilities_schema.json
- This object contains
Per-Surface catalog selection (
beginRendering): The agent is now responsible for selecting which catalog to use for each UI surface. It signals its choice using the new optionalcatalogIdfield in thebeginRenderingmessage. If this field is omitted, the client must default to the Standard Catalog.- Relevant doc:
a2ui_protocol.md - Relevant schema: The change is reflected in
server_to_client.json.
- Relevant doc:
Catalog definition ID (
catalogId): To facilitate identification, the catalog definition schema itself now has a requiredcatalogIdfield.- Relevant schema:
catalog_description_schema.json
- Relevant schema:
Implementation guide for developers
For Agent (server) library developers
Your responsibilities are to process the client's declared capabilities and make a rendering choice.
Advertise capability: In the agent's capability card, add the
supportedCatalogIdsarray and theacceptsInlineCatalogs: trueparameter within the A2UI extension block to declare which catalogs you support and whether you can handle dynamic ones.Parse client capabilities: On every incoming A2A
Message, your library must parse thea2uiClientCapabilitiesobject in themetadatafield of the A2AMessageto determine which catalogs the client supports. You will get a list ofsupportedCatalogIdsand potentially a list ofinlineCatalogs.Choose a Catalog: Before rendering a UI, decide which catalog to use. Your choice must be one of the catalogs advertised by the client in the capabilities object.
Specify Catalog on render: When sending the
beginRenderingmessage for a surface, set thecatalogIdfield to the ID of your chosen catalog (e.g.,"https://my-company.com/inline_catalogs/my-custom-catalog"). If you do not set this field, you are implicitly requesting the use of the standard catalog.Generate compliant UI: Ensure that all components generated in subsequent
surfaceUpdatemessages for that surface conform to the properties and types defined in the chosen catalog.
For Renderer (client) library developers
Your responsibilities are to accurately declare your capabilities and render surfaces using the catalog selected by the agent.
Declare capabilities on every request: For every A2A
Messageyour application sends, your library must inject thea2uiClientCapabilitiesobject into themetadatafield of the A2AMessage.Populate
supportedCatalogIds: In the capabilities object, populate this array with the string identifiers of all pre-compiled catalogs your renderer supports. If your renderer supports the standard catalog for v0.8, you should include its ID:https://a2ui.org/specification/v0_8/standard_catalog_definition.json.Provide
inlineCatalogs(optional): If your renderer supports dynamically generating or defining catalogs at runtime, include their full, valid Catalog Definition Documents in theinlineCatalogsarray.Process
beginRendering: When your renderer receives abeginRenderingmessage, it must inspect the newcatalogIdfield.Select Catalog for surface:
- If
catalogIdis present, use the corresponding catalog to render that surface. Your renderer must be able to look up the catalog from its pre-compiled list or from the inline definitions it just sent. - If
catalogIdis absent, you must default to using the Standard Catalog for v0.8 for that surface.
- If
Manage multiple Catalogs: Your renderer must be architected to handle multiple surfaces being rendered with different catalogs simultaneously. A dictionary mapping
surfaceIdto the chosencatalogis a common approach.
Specification V0 9 Docs A2ui Custom Functions (`specification/v0_9/docs/a2ui_custom_functions.md`)
Defining Custom Functions
A2UI functions are defined inside a Catalog. When defining your own catalog, you can include custom functions that are specific to your application or design system.
This guide demonstrates how to define a string trim function and a hardware query function (getScreenResolution) in your catalog.
1. Define the Catalog
Create a JSON Schema file (e.g., my_catalog.json) that defines your
function parameters.
Use the functions property to define a map of function schemas.
/* Detailed source-code truncated for AI context efficiency. */2. Make the functions available
The FunctionCall definition refers to a catalog-agnostic reference.
In your catalog, you simply need to define the anyFunction reference:
{
"$defs": {
"anyFunction": {
"oneOf": [{"$ref": "#/functions/trim"}, {"$ref": "#/functions/getScreenResolution"}]
}
}
}If you want to incorporate functions defined in the [catalogs/basic/catalog.json],
those can be added too:
{
"$defs": {
"anyFunction": {
"oneOf": [
{"$ref": "#/functions/trim"},
{"$ref": "#/functions/getScreenResolution"},
{"$ref": "catalogs/basic/catalog.json#/$defs/anyFunction"}
]
}
}
}How Validation Works
When a FunctionCall is validated:
- Discriminator Lookup: The validator looks at the
callproperty of the
object. - Schema Matching:
- If
callis "length", it matchesFunctions->length
and validates the named arguments inargsagainst the length rules. - If
callis "trim", it matchesCustomFunctions->trimand
validates against your custom rules. - If
callis "unknownFunc", validation FAILS immediately (strict mode).
- If
This strict-by-default approach ensures typos are caught early, while the
modular structure makes it easy to add new capabilities with full type safety.
Specification V0 9 Docs A2ui Extension Specification (`specification/v0_9/docs/a2ui_extension_specification.md`)
A2UI (Agent-to-Agent UI) Extension spec v0.9
Overview
This document is intended for developers implementing the A2UI A2A extension. The extension adds A2UI v0.9 support to A2A, a format for agents to send streaming, interactive user interfaces to clients.
Note that A2UI extension activation is optional as clients and agents can negotiate A2UI support using A2A message.metadata["a2uiClientCapabilities"] which is attached to every A2A message from the client and contains the supported protocol version and catalogs. Agents advertising A2UI support in their AgentCard is encouraged as clients may rely on it to determine if they should send message.metadata["a2uiClientCapabilities"], however it is not explicitly required.
Extension URI
The URI of this extension is https://a2ui.org/a2a-extension/a2ui/v0.9
This URI is the canonical way to communicate protocol versioning between clients and agents. The extension URI explicitly encodes the version (e.g., v0.9). A client requesting this specific URI indicates it supports the v0.9 schema format.
Agent Card
Agents are encouraged to advertise their A2UI capabilities in their AgentCard within the AgentCapabilities.extensions list. This advertisement is optional, but it informs the client whether to send message.metadata["a2uiClientCapabilities"]. The params object defines the agent's specific UI support and corresponds directly to the Server Capabilities Schema.
Example AgentCard payload:
{
"name": "Dashboard Agent",
"description": "Agent capable of generating dynamic UI dashboards.",
"capabilities": {
"extensions": [
{
"uri": "https://a2ui.org/a2a-extension/a2ui/v0.9",
"description": "Ability to render A2UI v0.9",
"required": false,
"params": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"https://my-company.com/a2ui/v0.9/my_custom_catalog.json"
],
"acceptsInlineCatalogs": true
}
}
]
}
}The params object corresponds to the v0.9 object in the server_capabilities.json schema:
params.supportedCatalogIds(optional): An array of strings, where each string is an ID identifying a Catalog Definition Schema that the agent can generate. This is not necessarily a resolvable URI.params.acceptsInlineCatalogs(optional): A boolean indicating if the agent can accept aninlineCatalogsarray in the client'sa2uiClientCapabilities. If omitted, this defaults tofalse.
A2A Extension activation
Activating the A2UI extension is optional. Clients and agents can negotiate A2UI support using message.metadata["a2uiClientCapabilities"] A2A DataPart.data.metadata["mimeType"] = "application/a2ui+json".
Specifically:
- If a client includes
message.metadata["a2uiClientCapabilities"], the agent can use this object to determine the supported A2UI protocol version and catalogs. - If an agent returns A2A A2A
DataPart.data.metadata["mimeType"] = "application/a2ui+json", the client knows the payload contains A2UI messages.
While explicit activation is not required, clients can still explicitly activate the extension using the transport-defined A2A extension activation mechanism. The A2A Extensions Guide defines this process.
Note: You should not use accepted_output_modes: ['a2ui'] (which is not an A2UI standard) to trigger A2UI.
JSON-RPC and HTTP transports
To activate the A2UI A2A Extension, the X-A2A-Extensions HTTP header includes the extension URI.
Example HTTP SendMessageRequest:
POST /v1/messages HTTP/1.1
Host: agent.example.com
X-A2A-Extensions: https://a2ui.org/a2a-extension/a2ui/v0.9
Content-Type: application/json
{
"message": {
"parts": [
{
"text": "Hello, show me the dashboard"
}
]
}
}To see how the agent parses the extension URI, see extension.py.
GRPC transport
To activate the A2UI A2A Extension, the client adds the extension URI to A2A sendMessageParams.metadata["X-A2A-Extensions"].
Example gRPC SendMessageRequest:
{
"metadata": {
"X-A2A-Extensions": "https://a2ui.org/a2a-extension/a2ui/v0.9"
},
"message": {
"parts": [
{
"text": "Hello, show me the dashboard"
}
]
}
}
}A2A Client to Server Metadata
Clients attach a2uiClientCapabilities and a2uiClientDataModel to A2A messages to communicate their state and supported catalogs.
`a2uiClientCapabilities`
The client sends sendMessageRequest.message["a2uiClientCapabilities"] = Client Capabilities Schema to advertise which catalogs the renderer supports.
Example SendMessageRequest with Capabilities:
{
"message": {
"parts": [
{
"text": "Show me the dashboard."
}
],
"metadata": {
"a2uiClientCapabilities": {
"v0.9": {
"supportedCatalogIds": [
"https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"https://my-company.com/a2ui/v0.9/my_custom_catalog.json"
]
}
}
}
}
}`a2uiClientDataModel`
When a surface enables Data Model Sync, the client sends sendMessageRequest.message["a2uiClientDataModel"] = Client Data Model Schema on every message. This model provides the agent with the latest UI state. For more details, see the Actions Guide.
Example SendMessageRequest with Data Model:
{
"message": {
"parts": [
{
"text": "Submit the form."
}
],
"metadata": {
"a2uiClientDataModel": {
"version": "v0.9",
"surfaces": {
"main_surface_id": {
"user_id": "12345",
"email": "[email protected]"
}
}
}
}
}
}Data encoding
Agents and clients encode A2UI messages as an A2A DataPart.
To identify a DataPart as containing A2UI data, it must have the following metadata:
DataPart.data.metadata["mimeType"] = "application/a2ui+json"
The data field of the DataPart contains a list of A2UI JSON messages (e.g., createSurface, updateComponents, action). It MUST be an array of messages.
Processing Rules
The data field contains a list of messages. This list is NOT a transactional unit. Receivers (both Clients and Agents) MUST process messages in the list sequentially.
If a single message in the list fails to validate or apply (e.g., due to a schema violation or invalid reference), the receiver SHOULD report/log the error for that specific message and MUST continue processing the remaining messages in the list.
Atomicity is guaranteed only at the individual message level. However, for a better user experience, a renderer SHOULD NOT repaint the UI until all messages in the list have been processed. This prevents intermediate states from flickering to the user.
Server-to-client messages
When an agent sends a message to a client (or another agent acting as a client/renderer), the data payload must validate against the Server-to-Client Message List Schema.
Example DataPart:
{
"data": [
{
"version": "v0.9",
"createSurface": {
"surfaceId": "example_surface",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"
}
},
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "example_surface",
"components": [
{
"Text": {
"id": "root",
"text": "Hello!"
}
}
]
}
}
],
"kind": "data",
"metadata": {
"mimeType": "application/a2ui+json"
}
}Client-to-server events
When a client (or an agent forwarding an event) sends a message to an agent, it also uses a DataPart with the same application/a2ui+json MIME type. However, the data payload must validate against the Client-to-Server Message List Schema.
Example action DataPart:
{
"data": [
{
"version": "v0.9",
"action": {
"name": "submit_form",
"surfaceId": "contact_form_1",
"sourceComponentId": "submit_button",
"timestamp": "2026-01-15T12:00:00Z",
"context": {
"email": "[email protected]"
}
}
}
],
"kind": "data",
"metadata": {
"mimeType": "application/a2ui+json"
}
}Specification V0 9 Docs A2ui Protocol (`specification/v0_9/docs/a2ui_protocol.md`)
<h1>A2UI (Agent to UI) Protocol v0.9</h1>A Specification for a JSON-Based, Streaming UI Protocol.
Version: 0.9
Status: Stable
Created: Nov 20, 2025
A Specification for a JSON-Based, Streaming UI Protocol
Introduction
The A2UI Protocol is designed for dynamically rendering user interfaces from a stream of JSON objects sent from a server (Agent). Its core philosophy emphasizes a clean separation of UI structure and application data, enabling progressive rendering as the client processes each message.
Communication occurs via a stream of JSON objects. The client parses each object as a distinct message and incrementally builds or updates the UI. The server-to-client protocol defines four message types:
createSurface: Signals the client to create a new surface and begin rendering it.updateComponents: Provides a list of component definitions to be added to or updated in a specific surface.updateDataModel: Provides new data to be inserted into or to replace a surface's data model.deleteSurface: Explicitly removes a surface and its contents from the UI.
Changes from previous versions
Version 0.9 of the A2UI protocol represents a philosophical shift from previous versions. While v0.8 was optimized for LLMs that support structured output, v0.9 is designed to be embedded directly within a model's prompt. The LLM is then asked to produce JSON that matches the provided examples and schema descriptions.
This "prompt-first" approach offers several advantages:
- Richer schema: The protocol is no longer limited by the constraints of structured output formats. This allows for more readable, complex, and expressive component catalogs.
- Modularity: The schema is now refactored into separate, more manageable components (e.g., [
common_types.json], [catalogs/basic/catalog.json], [server_to_client.json]), improving maintainability and modularity.
The main disadvantage of this approach is that it requires more complex post-generation validation, as the LLM is not strictly constrained by the schema. This requires robust error handling and correction, so the system can identify discrepancies and attempt to fix them before rendering, or request a retry or correction from the LLM.
See the evolution guide for a detailed explanation of the differences between v0.8 and v0.9.
Protocol overview & data flow
The A2UI protocol uses a unidirectional stream of JSON messages from the server to the client to describe and update the UI. The client consumes this stream, builds the UI, and renders it. User interactions are handled separately, typically by sending events to a different endpoint, which may in turn trigger new messages on the UI stream.
Here is an example sequence of events (which don't have to be in exactly this order):
- Create Surface: The server sends a
createSurfacemessage to initialize the surface. - Update Surface: Once a surface has been created, the server sends one or more
updateComponentsmessages containing the definitions for all the components that will be part of the surface. - Update Data Model: Once a surface has been created, the server can send
updateDataModelmessages at any time to populate or change the data that the UI components will display. - Render: The client renders the UI for the surface, using the component definitions to build the structure and the data model to populate the content.
- Dynamic updates: As the user interacts with the application or as new information becomes available, the server can send additional
updateComponentsandupdateDataModelmessages to dynamically change the UI. - Delete Surface: When a UI region is no longer needed, the server sends a
deleteSurfacemessage to remove it.
sequenceDiagram
participant Server
participant Client
Server->>+Client: 1. createSurface(surfaceId: "main")
Server->>+Client: 2. updateComponents(surfaceId: "main", components: [...])
Server->>+Client: 3. updateDataModel(surfaceId: "main", path: "/user", value: "Alice")
User->>+Client: Interact with UI (e.g. click button)
Client->>+Server: action(name: "submit", context: {...})
Client-->>-Server: (UI is displayed)
Note over Client, Server: Time passes, user interacts, or new data arrives...
Server->>+Client: 4. updateComponents or updateDataModel (Dynamic Update)
Note right of Client: Client re-renders the UI to reflect changes
Client-->>-Server: (UI is updated)
Server->>+Client: 5. deleteSurface(surfaceId: "main")
Note right of Client: Client removes the UI for the "main" surface
Client-->>-Server: (UI is gone)Transport decoupling
The A2UI protocol is designed to be transport-agnostic. It defines the JSON message structure and the semantic contract between the server (Agent) and the client (Renderer), but it does not mandate a specific transport layer.
The transport contract
To support A2UI, a transport layer must fulfill the following contract:
- Reliable delivery: Messages must be delivered in the order they were generated. A2UI relies on stateful updates (e.g., creating a surface before updating it), so out-of-order delivery can corrupt the UI state.
- Message framing: The transport must clearly delimit individual JSON envelope messages (e.g., using newlines in JSONL, WebSocket frames, or SSE events).
- Metadata support: The transport must provide a mechanism to associate metadata with messages. This is critical for:
- Data model synchronization: The
sendDataModelfeature requires the client to send the current data model state as metadata alongside user actions. - Capabilities exchange: Client capabilities (supported catalogs, custom components) and Server capabilities are exchanged via metadata or transport-specific handshakes (like Agent Cards in A2A or initialization in MCP).
- Data model synchronization: The
- Bidirectional capability (optional): While the rendering stream is unidirectional (Server -> Client), interactive applications require a return channel for
actionmessages (Client -> Server).
Transport bindings
While A2UI is agnostic, it is most commonly used with the following transports.
A2A (Agent2Agent) binding
A2A (Agent-to-Agent) is an excellent transport option for A2UI in agentic systems, extending A2A with additional payloads.
A2A is uniquely capable of handling remote agent communication, and can also provide a secure and efficient transport between an agentic backend and front end application.
- Message mapping: Each A2UI envelope (e.g.,
updateComponents) corresponds to the payload of a single A2A message Part. - Metadata:
- Data model: When
sendDataModelis active, the client'sa2uiClientDataModelobject is placed in themetadatafield of the A2A message. - Capabilities: The
a2uiClientCapabilitiesobject is placed in themetadatafield of every A2AMessagesent from the client to the server.
- Data model: When
- Context: A2UI sessions typically map to A2A
contextId. All messages for a set of related surfaces should share the samecontextId.
AG UI (Agent to User Interface) binding
AG-UI is also an excellent transport option for A2UI AgentβUser Interaction protocol.
AG UI provides convenient integrations into many agent frameworks and frontends. AG UI provides low latency and shared state message passing between front ends and agentic backends.
Other transports
A2UI can also be carried over:
- MCP (Model Context Protocol): Delivered as tool outputs or resource subscriptions.
- SSE with JSON RPC: Standard server-sent events for web integrations that support streaming, and JSON RPC for client-server communication.
- WebSockets: For bidirectional, real-time sessions.
- REST: For simple use case, REST APIs will work but lack streaming capabilities.
The protocol schemas
A2UI v0.9 is defined by three interacting JSON schemas.
Common types
The [common_types.json] schema defines reusable primitives used throughout the protocol.
DynamicString/DynamicNumber/DynamicBoolean/DynamicStringList: The core of the data binding system. Any property that can be bound to data is defined as aDynamic*type. It accepts either a literal value, apathstring ([JSON Pointer]), or aFunctionCall(function call).ChildList: Defines how containers hold children. It supports:array: A static array ofComponentIdcomponent references.object: A template for generating children from a data binding list (requires a templatecomponentIdand a data bindingpath).
ComponentId: A reference to the unique ID of another component within the same surface.
Server to client message structure: the envelope
The [server_to_client.json] schema is the top-level entry point. Every message streamed by the server must validate against this schema. It handles the message dispatching.
The Basic Catalog
The [catalogs/basic/catalog.json] schema contains the definitions for all specific UI components (e.g., Text, Button, Row), functions (e.g., required, email), and the theme schema.
Swappable Catalogs & Validation:
The [server_to_client.json] envelope schema is designed to be catalog-agnostic. It references components and themes using a placeholder filename: catalog.json (specifically $ref: "catalog.json#/$defs/anyComponent" and $ref: "catalog.json#/$defs/theme").
To validate A2UI messages:
- Basic Catalog: Map
catalog.jsontocatalogs/basic/catalog.json. - Client Catalog: Map
catalog.jsonto your own catalog file (e.g.,my_company_catalog.json).
This indirection allows the same core envelope schema to be used with any compliant component catalog without modification.
Defining your own catalog allows you to restrict the agent to using exactly the components and visual language that exist in your application. To use your own catalog, simply include it in the prompt in place of the basic catalog. It should have the same form as the basic catalog and use common elements in the [common_types.json] schema.
Validator compliance when defining catalogs
To ensure that automated validators can verify the integrity of your UI tree (checking that parents reference existing children), any catalog you define MUST adhere to the following strict typing rules:
Single child references: Any property that holds the ID of another component MUST use the
ComponentIdtype defined incommon_types.json.- Use:
"$ref": "common_types.json#/$defs/ComponentId" - Do NOT use:
"type": "string"
- Use:
List references: Any property that holds a list of children or a template MUST use the
ChildListtype.- Use:
"$ref": "common_types.json#/$defs/ChildList"
- Use:
Validators determine which fields represent structural links by looking for these specific schema references. If you use a raw string type for an ID, the validator will treat it as static text (like a URL or label) and will not check if the target component exists.
Envelope message structure
The envelope defines four primary message types, and every message streamed by the server must be a JSON object containing exactly one of the following keys: createSurface, updateComponents, updateDataModel, or deleteSurface. The key indicates the type of message, and these are the messages that make up each message in the protocol stream.
`createSurface`
This message signals the client to create a new surface and begin rendering it. A surface must be created before any updateComponents or updateDataModel messages can be sent to it. While typically achieved by the agent sending a createSurface message, an agent may skip this if it knows the surface has already been created (e.g., by another agent). Once a surface is created, its surfaceId and catalogId are fixed; to reconfigure them, the surface must be deleted and recreated.
It is an error to try to create a surface with a surfaceId that already exists without first deleting it; surfaceId must be globally unique for the renderer's lifetime. Orchestrators with subagents are empowered to manage surface IDs as needed to prevent conflicts (e.g., prefixing the subagent's name to the surfaceId or requiring subagents to use UUIDs).
One of the components in one of the component lists MUST have an id of root to serve as the root of the component tree.
Properties:
surfaceId(string, required): The unique identifier for the UI surface to be rendered. This must be globally unique for the renderer's lifetime.catalogId(string, required): A string that uniquely identifies the catalog (components and functions) used for this surface. Note thatcatalogIdis a string identifier, not a resolvable URI; while it is conventionally formatted as a URI (e.g.,https://mycompany.com/1.0/somecatalog) to avoid naming collisions across organizations, it does not need to point to any deployed resource or downloadable file. Client and server developers must agree on shared catalogs with well-known IDs in order to build systems that are compatible with each other.theme(object, optional): A JSON object containing theme parameters (e.g.,primaryColor) defined in the catalog's theme schema.sendDataModel(boolean, optional): If true, the client will send the full data model of this surface in the metadata of every message sent to the server (via the Transport's metadata mechanism). This ensures the surface owner receives the full current state of the UI alongside the user's action or query. Defaults to false.
Example:
{
"version": "v0.9",
"createSurface": {
"surfaceId": "user_profile_card",
"catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json",
"theme": {
"primaryColor": "#00BFFF"
},
"sendDataModel": true
}
}`updateComponents`
This message provides a list of UI components to be added to or updated within a specific surface. The components are provided as a flat list, and their relationships are defined by ID references in an adjacency list. This message may only be sent to a surface that has already been created. Note that components may reference children or data bindings that do not yet exist; clients should handle this gracefully by rendering placeholders (progressive rendering).
Properties:
surfaceId(string, required): The unique identifier for the UI surface to be updated. This is typically a name with meaning (e.g. "user_profile_card"), and it has to be unique within the context of the GenUI session.components(array, required): A list of component objects. The components are provided as a flat list, and their relationships are defined by ID references in an adjacency list.
Example:
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "user_profile_card",
"components": [
{
"id": "root",
"component": "Column",
"children": ["user_name", "user_title"]
},
{
"id": "user_name",
"component": "Text",
"text": "John Doe"
},
{
"id": "user_title",
"component": "Text",
"text": "Software Engineer"
}
]
}
}`updateDataModel`
This message is used to send or update the data that populates the UI components. It allows the server to change the UI's content without resending the entire component structure. The updateDataModel message replaces the value at the specified path with the new content. If path is omitted (or is /), the entire data model for the surface is replaced.
Properties:
surfaceId(string, required): The unique identifier for the UI surface this data model update applies to.path(string, optional): A JSON Pointer to the location in the data model to update. Defaults to/.value(any, optional): The new value for the specified path. If omitted, the key atpathis removed.
Example:
{
"version": "v0.9",
"updateDataModel": {
"surfaceId": "user_profile_card",
"path": "/user/name",
"value": "Jane Doe"
}
}`deleteSurface`
This message instructs the client to remove a surface and all its associated components and data from the UI.
Properties:
surfaceId(string, required): The unique identifier for the UI surface to be deleted.
Example:
{
"version": "v0.9",
"deleteSurface": {
"surfaceId": "user_profile_card"
}
}Example Stream
The following example demonstrates a complete interaction to render a Contact Form, expressed as a JSONL stream.
/* Detailed source-code truncated for AI context efficiency. */Component model
A2UI's component model is designed for flexibility, separating the protocol's structure from the set of available UI components.
The component object
Each object in the components array of an updateComponents message defines a single UI component. It has the following structure:
id(ComponentId, required): A unique string that identifies this specific component instance. This is used for parent-child references.component(string, required): Specifies the component's type (e.g.,"Text").- Component Properties: Other properties relevant to the specific component type (e.g.,
text,url,children) are included directly in the component object.
This structure is designed to be both flexible and strictly validated.
The component catalog
The set of available UI components and functions is defined in a Catalog. The basic catalog is defined in [catalogs/basic/catalog.json]. While the Basic Catalog is useful for starting out, most production applications will define their own catalog to reflect their specific design system. The server must generate messages that conform to the catalog understood by the client.
Catalog Identification & Compatibility
Each catalog is identified by a catalogId string. The catalogId is a string identifier used for matching catalogs between the client and server. While it is conventional to format catalog IDs as URIs (e.g., https://mycompany.com/catalogs/v1) to prevent naming collisions across organizations, a catalogId is not required to be a resolvable network resource.
Because A2UI catalogs are represented as JSON Schema documents, catalog definitions should include both $id (used by JSON Schema tooling) and catalogId (used by A2UI SDKs and catalog negotiation), and both fields should be set to the same URI.
It is up to client and server developers to agree on shared catalogs with well-known IDs in order to build systems that are compatible with each other.
UI composition: the adjacency list model
The A2UI protocol defines the UI as a flat list of components. The tree structure is built implicitly using ID references. This is known as an adjacency list model.
Container components (like Row, Column, List, and Card) have properties that reference the id of their child component(s). The client is responsible for storing all components in a map (e.g., Map<String, Component>) and recreating the tree structure at render time.
This model allows the server to send component definitions in any order. Rendering can begin as soon as the root component is defined, with the client filling in or updating the rest of the tree progressively as additional definitions arrive.
There must be exactly one component with the ID root in the component tree, acting as the root of the component tree. Until that component is defined, other component updates will have no visible effect, and they will be buffered until a root component is defined. Once a root component is defined, the client is responsible for rendering the tree in the best way possible based on the available data, skipping invalid references.
flowchart TD
subgraph "Server Stream"
A("<b>updateComponents</b><br>components: [root, title, button]")
end
subgraph "Client-Side Buffer (Map)"
C("root: {id: 'root', component: 'Column', children: ['title', 'button']}")
D("title: {id: 'title', component: 'Text', text: 'Welcome'}")
E("button: {id: 'button', component: 'Button', child: 'button_label'}")
end
subgraph "Rendered Widget Tree"
F(Column) --> G(Text: 'Welcome')
F --> H(Button)
end
A -- "Parsed and stored" --> C
A -- "Parsed and stored" --> D
A -- "Parsed and stored" --> EDefining actions
Interactive components (like Button) use an action property to define what happens when the user interacts with them. Actions can either trigger an event sent to the server or execute a local client-side function.
Server actions
To send an event to the server, use the event property within the action object. It requires a name and an optional context.
{
"component": "Button",
"text": "Submit",
"action": {
"event": {
"name": "submit_form",
"context": {
"itemId": "123"
}
}
}
}Local actions
To execute a local function, use the functionCall property within the action object. This property references a standard FunctionCall object.
{
"component": "Button",
"text": "Open Link",
"action": {
"functionCall": {
"call": "openUrl",
"args": {
"url": "${/url}"
}
}
}
}Data model representation: binding, scope
This section describes how UI components represent and reference data from the Data Model. A2UI relies on a strictly defined relationship between the UI structure (Components) and the state (Data Model), defining the mechanics of path resolution, variable scope during iteration.
Path resolution & scope
Data bindings in A2UI are defined using JSON Pointers ([RFC 6901]). How a pointer is resolved depends on the current Evaluation Scope.
[!NOTE]
A2UI extends JSON Pointer to support Relative Paths that do not start with a forward slash/. This is a deviation from strict RFC 6901 to support template-based list rendering.
Note on progressive rendering: During the initial streaming phase, data paths may resolve to
undefinedif theupdateDataModelmessage containing that data has not yet arrived. Renderers should handleundefinedvalues gracefully (e.g., by treating them as empty strings or showing a loading indicator) to support progressive rendering.
The root scope
By default, all components operate in the Root Scope.
- Paths starting with
/(e.g.,/user/profile/name) are Absolute Paths. They always resolve from the root of the Data Model, regardless of where the component is nested in the UI tree.
Collection scopes (relative paths)
When a container component (such as Column, Row, or List) utilizes the Template feature of ChildList, it creates a new Child Scope for each item in the bound array.
Template definition: When a container binds its children to a path (e.g.,
path: "/users"), the client iterates over the array found at that location.Scope instantiation: For every item in the array, the client instantiates the template component.
Relative resolution: Inside these instantiated components, any path that does not start with a forward slash
/is treated as a Relative Path.- A relative path
firstNameinside a template iterating over/usersresolves to/users/0/firstNamefor the first item,/users/1/firstNamefor the second, etc. - It is an error to use a non-numeric index on a path segment that refers to an array.
- A relative path
Mixing scopes: Components inside a Child Scope can still access the Root Scope by using an Absolute Path.
Example: scope resolution
Data model:
{
"company": "Acme Corp",
"employees": [
{"name": "Alice", "role": "Engineer"},
{"name": "Bob", "role": "Designer"}
]
}Component definition:
{
"id": "employee_list",
"component": "List",
"children": {
"path": "/employees",
"componentId": "employee_card_template"
}
},
{
"id": "employee_card_template",
"component": "Column",
"children": ["name_text", "company_text"]
},
{
"id": "name_text",
"component": "Text",
"text": { "path": "name" }
// "name" is Relative. Resolves to /employees/N/name
},
{
"id": "company_text",
"component": "Text",
"text": { "path": "/company" }
// "/company" is Absolute. Resolves to "Acme Corp" globally.
}Type conversion
When a non-string value is interpolated, the client converts it to a string:
- Numbers/Booleans: Standard string representation.
- null/undefined: An empty string
"". - Objects/Arrays: Stringified as JSON to ensure consistency across different client implementations.
Two-way binding & input components
Interactive components that accept user input (TextField, CheckBox, Slider, ChoicePicker, DateTimeInput) establish a Two-Way Binding with the Data Model.
The read/write contract
Unlike static display components (like Text), input components modify the client-side data model immediately upon user interaction.
- Read (Model -> View): When the component renders, it reads its value from the bound
path. If the Data Model is updated viaupdateDataModel, the component re-renders to reflect the new value. - Write (View -> Model): When the user interacts with the component (e.g., types a character, toggles a box), the client immediately updates the value at the bound
pathin the local Data Model.
Reactivity
Because the local Data Model is the single source of truth, updates from input components are reactive.
- If a
TextFieldis bound to/user/name, and a separateTextlabel is also bound to/user/name, the label must update in real-time as the user types in the text field.
Server synchronization
It is critical to note that Two-Way Binding is local to the client.
- User inputs (keystrokes, toggles) do not automatically trigger network requests to the server.
- The updated state is sent to the server only when a specific User Action is triggered (e.g., a
Buttonclick). - When an
actionis dispatched, thecontextproperty of the action can reference the modified