
I've dedicated considerable time to exploring A2UI, a protocol designed for agentic interfaces. My motivation is straightforward: the concept of dynamically generating user interfaces fascinates me, and even though this protocol is quite new (currently at 1.0 RC), I was driven to address a key question: “How can I integrate A2UI into my own application?”
For those unfamiliar with generative UIs, grasping the fundamental concepts might initially seem daunting. This article presents my framework for comprehending the entire ecosystem from an implementer's viewpoint, enabling you to adopt it in your projects.
My enthusiasm for the evolution of web applications is immense. This excitement led me to host a livestream titled Exploring the future of web apps last November, where I constructed my own framework for generating agentic web apps with Angular.
Important Context: The framework I developed during that session isn't as adaptable or modular (for use with other web frameworks, for instance) as A2UI, but the core architectural patterns align closely. If you find the A2UI documentation and this post challenging to follow, watching that livestream might clarify things. The overlapping ideas could help solidify your understanding.
Understanding A2UI
A2UI empowers AI agents to produce rich, interactive user interfaces that render seamlessly on web, mobile, and desktop platforms — without relying on arbitrary code execution. This capability is particularly appealing because A2UI delivers:
- Integrated in-app experiences: Interfaces that adapt fluidly to user requirements.
- Portable views: Ubiquitous interfaces that play a key role in Gemini web app offerings.
- Safety: No arbitrary code gets executed.
However, appreciating A2UI requires recognizing what it isn't. It's a protocol, not a framework.
This distinction carries significant weight. Since it's a protocol, adopting GenUI doesn't restrict you to a particular stack. It establishes the communication protocol between an LLM agent and a client application concerning UI elements, while leaving rendering decisions to the frontend technology of your choosing.
The High-Level Architecture of an A2UI Application
Before we examine the mechanics and implementation details, let's review the preliminary concepts and prerequisites.
Fundamentally, A2UI functions around a shared protocol or schema.
- Server (Agent-Side): Produces a message adhering to this schema to define the layout. The “agentic” nature comes from the message being dynamically generated by an AI agent responding to user requests.
- Client (Web/Mobile): Interprets that message and knows precisely how to build the interface.
The protocol's strength lies in its capacity to support clients built with various web and mobile frameworks. For this discussion, we'll concentrate on an Angular implementation.
To illustrate the data exchange, here's a sample of an A2UI message. If it looks unfamiliar, that's okay — we'll delve deeper shortly — but essentially, it's a JSON representation of the app structure:
{
"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"}
}
]
}
}
What You Need to Provide as a Developer
To simplify comprehension, let's suppose we can bypass all setup and boilerplate, leaving only the essential data (or metadata) needed to implement A2UI in your application. We'll revisit how this integrates into your app structure later.
For now, here's what you, the developer, must supply.
Server-Side (Agent)
Let's begin with the agent configuration on the server.
1. Catalog: Your Building Blocks
The catalog acts as the glossary of components available for your A2UI application. It resides on the server, informing the agent about the “building blocks” it can utilize when generating messages for the client.
These building blocks can be anything from simple fields like text inputs or icons to complex custom components. This catalog is structured using JSON Schema and includes the specifications for each element — such as its descriptions, variants, and accepted children.
To streamline onboarding, the A2UI team released a “basic catalog” with common components, but you also have the option (and often the need) to create your own. For this article, we'll examine two samples from the A2UI team.
- The restaurant_finder sample exclusively uses the standard catalog.
- Meanwhile, the rizzcharts example adopts its own custom catalog, given its dependence on specialized components like a Chart:
"Chart": {
"type": "object",
"allOf": [
{
"$ref": "common_types.json#/$defs/ComponentCommon"
},
{
"$ref": "#/$defs/CatalogComponentCommon"
},
{
"type": "object",
"description": "An interactive chart that uses a hierarchical list of objects for its data.",
"properties": {
"component": {
"const": "Chart"
},
"type": {
"type": "string",
"description": "The type of chart to render.",
"enum": [
"doughnut",
"pie"
]
},
"title": {
"$ref": "common_types.json#/$defs/DynamicString",
"description": "The title of the chart."
},
"chartData": {
"description": "The data for the chart, provided as a list of items. Can be a literal array or a data model path.",
"oneOf": [
{
"type": "array",
"items": {
"$ref": "#/$defs/ChartItem"
}
},
{
"$ref": "common_types.json#/$defs/DataBinding"
}
]
}
},
"required": [
"component",
"type",
"chartData"
]
}
],
"unevaluatedProperties": false
},
2. UI Description
This element provides a natural language explanation outlining which UI(s) the app can or should employ, their trigger conditions, and their visual design.
Consider how this is configured in the restaurant_finder prompt builder:
UI_DESCRIPTION = """
- If the query is for a list of restaurants, use the restaurant data you have already received from the `get_restaurants` tool to populate the `updateDataModel` message.
- IMPORTANT: When using updateDataModel to update items, you MUST specify `path: "/items"` in `updateDataModel`, and the `value` MUST be an array of restaurants.
- IMPORTANT: Always specify the path when using updateDataModel. The part message is ignored when the path is missing.
- If the number of restaurants is 5 or fewer, you MUST use the `SINGLE_COLUMN_LIST_EXAMPLE` template.
- If the number of restaurants is more than 5, you MUST use the `TWO_COLUMN_LIST_EXAMPLE` template.
- If the query is to book a restaurant (e.g., "USER_WANTS_TO_BOOK..."), you MUST use the `BOOKING_FORM_EXAMPLE` template.
- If the query is a booking submission (e.g., "User submitted a booking..."), you MUST use the `CONFIRMATION_EXAMPLE` template.
"""
3. Examples
At this point, the agent understands which UIs to create and what blocks are available, yet significant ambiguity remains. By offering examples through the few-shot prompting method, you can steer the agent. This approach clarifies the expected JSON message format and the resulting UI.
A notable illustration is the booking form example from the restaurant_finder sample. As that app relies solely on the basic catalog, a dedicated “booking form” component doesn't exist. Instead, this example demonstrates how to construct a booking form using foundational elements like inputs, columns, and buttons.
4. Agent Skills
Your UI descriptions can be further enhanced with “skills” to define specific user interactions, variations, and scenarios. For example, the rizzcharts sample creates a view_sales_by_category skill:
AgentSkill(
id="view_sales_by_category",
name="View Sales by Category",
description=(
"Displays a pie chart of sales broken down by product category for"
" a given time period."
),
tags=["sales", "breakdown", "category", "pie chart", "revenue"],
examples=[
"show my sales breakdown by product category for q3",
"What's the sales breakdown for last month?",
],
),
5. Action Mappings
Generating dynamic views is only one aspect. To create a functional application, we must address user interactions — such as clicks or submissions. This is where action mappings become relevant.
An action mapping provides a named action, initiated by a user event, that the agent can use to perform some activity. Given the ambiguity, let's examine the restaurant_finder submit_booking action:
elif action == "submit_booking":
restaurant_name = ctx.get("restaurantName", "Unknown Restaurant")
party_size = ctx.get("partySize", "Unknown Size")
reservation_time = ctx.get("reservationTime", "Unknown Time")
dietary_reqs = ctx.get("dietary", "None")
image_url = ctx.get("imageUrl", "")
query = (
f"User submitted a booking for {restaurant_name} for {party_size} people at"
f" {reservation_time} with dietary requirements: {dietary_reqs}. The image"
f" URL is {image_url}"
)
Observe the mechanism: we instruct the agent that “if it receives the submit_booking action, it should extract these parameters and submit this new natural language query back to itself.” We're not engineering fixed database operations or redirects; we're feeding a structured state update back to the AI.
How does the agent determine the next step? Well, as an AI agent, it autonomously decides — frequently by invoking tools.
6. Agent Tools
Tools are functions or external services that enable an agent to interface with its environment. They empower agents to execute complex operations — such as retrieving live data, making API calls, or managing databases. While tools aren't strictly part of A2UI and are more universal to agentic design, they are critical to the overall workflow.
For instance, the rizzcharts sample includes tools such as get_store_sales and get_sales_data.
You can also employ skills to link these tools and mappings, visible in the restaurant_finder skill registration:
skill = AgentSkill(
id="find_restaurants",
name="Find Restaurants Tool",
description=(
"Helps find restaurants based on user criteria (e.g., cuisine, location)."
),
tags=["restaurant", "finder"],
examples=["Find me the top 10 chinese restaurants in the US"],
)
7. Additional Metadata and Configuration Options
Beyond the core logic, your server-side agents need supplementary metadata to operate, including an app name, ID, and description. You'll also set choices for session management, memory, and inclusion of few-shot examples in the prompt context.
Client-Side in an Angular App
All the aspects covered thus far are server-side. Next, let's address the frontend, where you need to provide two key pieces of non-boilerplate information:
1. Custom Components Catalog
If your server-side agent creates custom components (like charts or maps), your client must know how to render them.
This step doesn't apply when using only standard elements. For example, the restaurant_finder app configures its Angular application to include the basic catalog, offering standard interface widgets such as rows, columns, text elements, and cards:
{
provide: A2UI_RENDERER_CONFIG,
useFactory: () => {
const injector = inject(Injector);
return {
catalogs: [new BasicCatalog()],
actionHandler: (action: A2uiClientAction) => injector.get(Client).handleAction(action),
};
},
},
However, with more specialized needs like the rizzcharts app, custom components become necessary. This project contains an a2ui-catalog directory housing the Angular implementations of the custom components defined in the JSON schemas.
It also includes a catalog.ts file that extends the basic catalog to register these custom components. This updated catalog is registered in the app configuration similarly.
Essential Guideline: The component definitions on your client must align perfectly with the catalog schemas and examples supplied to the server-side agent. Any mismatch will result in JSON that your client cannot process.
2. Theming
To make these agent-generated views appear native to your app, you can define CSS custom properties and themes. The restaurant_finder app, for instance, styles its dynamic views through restaurant-theme.css. Note that the CSS variables in that file target the “basic catalog” version of the widgets. With custom catalogs, styling can be handled in whatever manner you choose.
Connecting All the Pieces
I chose to center this examination on the specific developer inputs for several reasons:
- Focus on Principles: Concentrating on data schemas clarifies core concepts. It allows you to avoid getting lost in directory structures or class interfaces right away. The official A2UI docs handle the physical configuration—like setting up Angular to use A2UI—very well. My goal is that the examples and snippets here offer a practical connection.
- Declarative Approach: This method underscores a clear split between configuration and execution. I personally favor highly declarative systems. As an illustration, in my own sandbox app, I consolidated most of the metadata described here into a single JSON file, leading to excellent flexibility and easy maintenance.
- Alignment: It demonstrates how the various parts fit together. When building your own application, I strongly recommend establishing one source of truth for your agentic views. From that singular definition, you can generate UI descriptions, JSON catalogs, agent examples, and client components, keeping everything consistent.
- Automation Opportunities: Finally, it prompts consideration of automating these developer workflows.
The Next Step: Automated Agentics
The outlook for dynamic, user-centric interfaces is optimistic, and I'm eager to see your creations. I hope this text provides the foundational insight you need to embark on building the future of web apps today. For beginning with A2UI in Angular, refer to the official guide for setting up your Angular client with A2UI.
This content is based on Demystifying A2UI: How to Make AI Agents “Speak UI” in Your App, originally published in the Angular Blog on Medium.
