docs/en/learn/a2ui.mdx
A2UI is a declarative UI protocol extension for A2A that lets agents emit structured JSON messages describing interactive surfaces. Clients receive these messages and render them as rich UI components — forms, cards, lists, modals, and more — without the agent needing to know anything about the client's rendering stack.
A2UI is built on the A2A extension mechanism and identified by the URI https://a2ui.org/a2a-extension/a2ui/v0.8.
DataPart entries with the application/json+a2ui MIME typeAdd A2UIServerExtension to your A2AServerConfig to enable A2UI output:
from crewai import Agent
from crewai.a2a import A2AServerConfig
from crewai.a2a.extensions.a2ui import A2UIServerExtension
agent = Agent(
role="Dashboard Agent",
goal="Present data through interactive UI surfaces",
backstory="Expert at building clear, actionable dashboards",
llm="gpt-4o",
a2a=A2AServerConfig(
url="https://your-server.com",
server_extensions=[A2UIServerExtension()],
),
)
Add A2UIClientExtension to your A2AClientConfig to enable A2UI rendering:
from crewai import Agent
from crewai.a2a import A2AClientConfig
from crewai.a2a.extensions.a2ui import A2UIClientExtension
agent = Agent(
role="UI Coordinator",
goal="Coordinate tasks and render agent responses as rich UI",
backstory="Expert at presenting agent output in interactive formats",
llm="gpt-4o",
a2a=A2AClientConfig(
endpoint="https://dashboard-agent.example.com/.well-known/agent-card.json",
client_extensions=[A2UIClientExtension()],
),
)
A2UI defines four server-to-client message types. Each message targets a surface identified by surfaceId.
{
"beginRendering": {
"surfaceId": "dashboard-1",
"root": "main-column",
"catalogId": "standard (v0.8)",
"styles": {
"primaryColor": "#EB6658"
}
}
}
{
"surfaceUpdate": {
"surfaceId": "dashboard-1",
"components": [
{
"id": "main-column",
"component": {
"Column": {
"children": { "explicitList": ["title", "content"] },
"alignment": "start"
}
}
},
{
"id": "title",
"component": {
"Text": {
"text": { "literalString": "Dashboard" },
"usageHint": "h1"
}
}
}
]
}
}
{
"dataModelUpdate": {
"surfaceId": "dashboard-1",
"path": "/data/model",
"contents": [
{
"key": "userName",
"valueString": "Alice"
},
{
"key": "score",
"valueNumber": 42
}
]
}
}
{
"deleteSurface": {
"surfaceId": "dashboard-1"
}
}
A2UI ships with 18 standard components organized into three categories:
| Component | Description | Required Fields |
|---|---|---|
| Text | Renders text with optional heading/body hints | text (StringBinding) |
| Image | Displays an image with fit and size options | url (StringBinding) |
| Icon | Renders a named icon from a set of 47 icons | name (IconBinding) |
| Video | Embeds a video player | url (StringBinding) |
| AudioPlayer | Embeds an audio player with optional description | url (StringBinding) |
| Component | Description | Required Fields |
|---|---|---|
| Row | Horizontal flex container | children (ChildrenDef) |
| Column | Vertical flex container | children (ChildrenDef) |
| List | Scrollable list (vertical or horizontal) | children (ChildrenDef) |
| Card | Elevated container for a single child | child (str) |
| Tabs | Tabbed container | tabItems (list of TabItem) |
| Divider | Visual separator (horizontal or vertical) | — |
| Modal | Overlay triggered by an entry point | entryPointChild, contentChild (str) |
| Component | Description | Required Fields |
|---|---|---|
| Button | Clickable button that triggers an action | child (str), action (Action) |
| CheckBox | Boolean toggle with a label | label (StringBinding), value (BooleanBinding) |
| TextField | Text input with type and validation options | label (StringBinding) |
| DateTimeInput | Date and/or time picker | value (StringBinding) |
| MultipleChoice | Selection from a list of options | selections (ArrayBinding), options (list) |
| Slider | Numeric range slider | value (NumberBinding) |
Components reference values through bindings rather than raw literals. This allows surfaces to update dynamically when the data model changes.
There are two ways to bind a value:
{
"surfaceUpdate": {
"surfaceId": "profile-1",
"components": [
{
"id": "greeting",
"component": {
"Text": {
"text": { "path": "/data/model/userName" },
"usageHint": "h2"
}
}
},
{
"id": "status",
"component": {
"Text": {
"text": { "literalString": "Online" },
"usageHint": "caption"
}
}
}
]
}
}
In this example, greeting reads the user's name from the data model (updated via dataModelUpdate), while status uses a hardcoded literal.
Interactive components like Button trigger userAction events that flow back to the server. Each action includes a name, the originating surfaceId and sourceComponentId, and an optional context with key-value pairs.
{
"userAction": {
"name": "submitForm",
"surfaceId": "form-1",
"sourceComponentId": "submit-btn",
"timestamp": "2026-03-12T10:00:00Z",
"context": {
"selectedOption": "optionA"
}
}
}
Action context values can also use path bindings to send current data model values back to the server:
{
"Button": {
"child": "confirm-label",
"action": {
"name": "confirm",
"context": [
{
"key": "currentScore",
"value": { "path": "/data/model/score" }
}
]
}
}
}
Use validate_a2ui_message to validate server-to-client messages and validate_a2ui_event for client-to-server events:
from crewai.a2a.extensions.a2ui import validate_a2ui_message
from crewai.a2a.extensions.a2ui.validator import (
validate_a2ui_event,
A2UIValidationError,
)
# Validate a server message
try:
msg = validate_a2ui_message({"beginRendering": {"surfaceId": "s1", "root": "r1"}})
except A2UIValidationError as exc:
print(exc.errors)
# Validate a client event
try:
event = validate_a2ui_event({
"userAction": {
"name": "click",
"surfaceId": "s1",
"sourceComponentId": "btn-1",
"timestamp": "2026-03-12T10:00:00Z",
}
})
except A2UIValidationError as exc:
print(exc.errors)