Skip to main content

A2UI Protocol Reference

A2UI (Agent-to-User Interface) is an open protocol for AI agents to generate safe, declarative user interfaces.


Overview

A2UI enables agents to generate native UI components instead of HTML, providing:

  • Security: Declarative JSON, not executable code
  • LLM-Friendly: Optimized for token-by-token generation
  • Platform-Agnostic: Works with React, Flutter, Vue, Angular
  • Progressive: Streams UI components as generated
  • Accessible: Native components inherit host accessibility

A2UI vs HTML

AspectHTMLA2UI
Format<button>Click</button>{"type": "button", "label": "Click"}
SecurityXSS risks, script injectionSchema-validated, no execution
StreamingMust complete full documentStreams JSON objects progressively
Type SafetyRuntime errorsCompile-time validation
AccessibilityManual ARIA attributesNative component inheritance
PlatformWeb onlyAny platform with renderer

Protocol Flow

┌──────────────┐
│ User Request │
│ "Show status"│
└────────┬─────┘


┌──────────────────────┐
│ Agent Processing │
│ Generates A2UI JSON │
└────────┬─────────────┘

A2UI Component Tree
{
"type": "container",
"children": [...]
}


┌──────────────────────┐
│ A2UI Client Parser │
│ Validates + Renders │
└────────┬─────────────┘


┌──────────────────────┐
│ Native UI Components │
│ (React, Flutter, etc)│
└──────────────────────┘

Component Schema

Basic Structure

{
"type": "component_type",
"properties": {...},
"children": [...]
}

Core Components

Text

{
"type": "text",
"content": "Hello, World!",
"style": "heading|body|caption"
}

Button

{
"type": "button",
"label": "Click Me",
"action": "action_name",
"variant": "primary|secondary|danger",
"disabled": false
}

Container

{
"type": "container",
"layout": "vertical|horizontal|grid",
"gap": "small|medium|large",
"children": [...]
}

Card

{
"type": "card",
"title": "Card Title",
"subtitle": "Optional subtitle",
"children": [...]
}

Table

{
"type": "table",
"columns": ["Name", "Status", "Progress"],
"rows": [
["VPC", "✅ Complete", "100%"],
["Cluster", "⏳ Running", "60%"]
]
}

List

{
"type": "list",
"ordered": false,
"items": [
{"text": "Item 1", "icon": "check"},
{"text": "Item 2", "icon": "pending"}
]
}

Progress

{
"type": "progress",
"value": 75,
"max": 100,
"label": "Deployment Progress"
}

Input

{
"type": "input",
"name": "cluster_name",
"label": "Cluster Name",
"placeholder": "Enter name...",
"required": true
}

Select

{
"type": "select",
"name": "region",
"label": "Region",
"options": [
{"value": "us-east-1", "label": "US East"},
{"value": "eu-west-1", "label": "EU West"}
]
}

Complex Examples

Status Dashboard

{
"type": "container",
"layout": "vertical",
"children": [
{
"type": "text",
"content": "Infrastructure Status",
"style": "heading"
},
{
"type": "container",
"layout": "grid",
"columns": 3,
"children": [
{
"type": "card",
"title": "VPC",
"children": [
{"type": "text", "content": "vpc-12345"},
{"type": "badge", "label": "Active", "color": "green"}
]
},
{
"type": "card",
"title": "Cluster",
"children": [
{"type": "text", "content": "eks-prod-01"},
{"type": "badge", "label": "Running", "color": "green"}
]
},
{
"type": "card",
"title": "Nodes",
"children": [
{"type": "text", "content": "5 / 5 healthy"},
{"type": "progress", "value": 100}
]
}
]
}
]
}

Approval Form

{
"type": "card",
"title": "Deployment Approval Required",
"children": [
{
"type": "text",
"content": "The following changes require your approval:",
"style": "body"
},
{
"type": "table",
"columns": ["Change", "Impact"],
"rows": [
["Add 3 nodes", "+$150/month"],
["Enable auto-scaling", "Variable cost"],
["Update security groups", "No cost"]
]
},
{
"type": "container",
"layout": "horizontal",
"gap": "medium",
"children": [
{
"type": "button",
"label": "Approve",
"action": "approve",
"variant": "primary"
},
{
"type": "button",
"label": "Reject",
"action": "reject",
"variant": "danger"
},
{
"type": "button",
"label": "Request Changes",
"action": "request_changes",
"variant": "secondary"
}
]
}
]
}

Deployment Progress

{
"type": "container",
"layout": "vertical",
"children": [
{
"type": "progress",
"value": 60,
"label": "Overall Progress"
},
{
"type": "list",
"items": [
{"text": "Build container image", "icon": "check", "status": "complete"},
{"text": "Push to registry", "icon": "check", "status": "complete"},
{"text": "Deploy to staging", "icon": "spinner", "status": "in_progress"},
{"text": "Run integration tests", "icon": "circle", "status": "pending"},
{"text": "Deploy to production", "icon": "circle", "status": "pending"}
]
}
]
}

Actions and Events

Defining Actions

{
"type": "button",
"label": "Deploy",
"action": {
"type": "submit",
"endpoint": "deploy",
"params": {
"environment": "production"
}
}
}

Action Types

TypeDescription
submitSubmit form data to agent
navigateNavigate to another view
dismissClose current modal/dialog
confirmShow confirmation dialog
copyCopy text to clipboard

Streaming

A2UI supports progressive rendering:

Stream 1: {"type": "container", "children": [
Stream 2: {"type": "text", "content": "Loading..."},
Stream 3: {"type": "progress", "value": 0}
Stream 4: ]}

// Update as data arrives
Stream 5: {"update": {"path": "children[0]", "value": {"type": "text", "content": "Processing VPC..."}}}
Stream 6: {"update": {"path": "children[1]", "value": {"type": "progress", "value": 25}}}

Validation

Schema Validation

interface A2UIComponent {
type: string;
id?: string;
children?: A2UIComponent[];
[key: string]: any;
}

function validateComponent(component: A2UIComponent): boolean {
// Check type is registered
if (!COMPONENT_REGISTRY[component.type]) {
return false;
}

// Validate props against schema
const schema = COMPONENT_REGISTRY[component.type].schema;
return validate(component, schema);
}

Security Validation

  • ✅ No script execution
  • ✅ No inline styles (use predefined themes)
  • ✅ No external URLs without allowlist
  • ✅ Action handlers are predefined
  • ✅ Sanitize all text content

React Implementation

import { A2UIRenderer, registerComponent } from '@a2ui/react';

// Register custom components
registerComponent('deployment-status', DeploymentStatusCard);

// Render A2UI JSON
function App() {
const [a2uiData, setA2uiData] = useState(null);

useEffect(() => {
// Receive A2UI from agent
agent.onMessage((data) => setA2uiData(data));
}, []);

return (
<A2UIRenderer
data={a2uiData}
onAction={(action) => agent.sendAction(action)}
theme="dark"
/>
);
}

Best Practices

  • ✅ Define all components in registry at startup
  • ✅ Stream JSON progressively for performance
  • ✅ Include fallback components for unknown types
  • ✅ Validate against schema before rendering
  • ✅ Provide keyboard navigation support
  • ✅ Test across platforms (web, mobile, desktop)
  • ✅ Use semantic component types
  • ✅ Keep component trees shallow when possible

External Resources