Skip to main content

What is A2UI?

A2UI (Agent-to-UI) is a declarative specification for generative UI that uses JSON schemas to describe structured UI components. Instead of hand-building React components for every use case, agents emit JSON specifications that your frontend interprets and renders. This approach provides:
  • Balanced flexibility - Wide vocabulary without custom components
  • Multi-platform support - Same spec renders on web, mobile, desktop
  • Scalability - Handle diverse use cases without linear codebase growth
  • Clean separation - Logic and presentation stay decoupled

How A2UI Works

  1. Agent emits A2UI JSON - Your backend sends structured JSON following the A2UI specification
  2. AG-UI streams it - The A2UI messages flow through AG-UI protocol as ActivityMessage objects
  3. Frontend renders it - CopilotKit’s A2UI renderer interprets the spec and displays components

Quick Start

Step 1: Install Dependencies

npm install @copilotkitnext/react @copilotkitnext/a2ui-renderer @google/a2ui

Step 2: Configure Frontend

Create an A2UI message renderer and pass it to CopilotKit:
app/page.tsx
"use client";

import { CopilotChat, CopilotKitProvider } from "@copilotkitnext/react";
import { createA2UIMessageRenderer } from "@copilotkitnext/a2ui-renderer";
import { theme } from "./theme";

const A2UIMessageRenderer = createA2UIMessageRenderer({ theme });

export default function Home() {
  return (
    <CopilotKitProvider
      runtimeUrl="/api/copilotkit"
      renderActivityMessages={[A2UIMessageRenderer]}
    >
      <main className="flex min-h-screen flex-col">
        <CopilotChat style={{ flex: 1 }} />
      </main>
    </CopilotKitProvider>
  );
}

Step 3: Define Your Theme

The theme controls how A2UI components render:
app/theme.ts
import { v0_8 } from "@google/a2ui";

export const theme: v0_8.Types.Theme = {
  components: {
    Button: {
      "layout-pt-2": true,
      "layout-pb-2": true,
      "layout-pl-3": true,
      "layout-pr-3": true,
      "border-br-12": true,
      "color-bgc-p30": true,
      "color-c-n100": true,
    },
    Card: {
      "border-br-9": true,
      "color-bgc-p100": true,
      "layout-p-4": true,
    },
    Text: {
      all: {
        "color-c-p30": true,
      },
      h1: {
        "typography-sz-tl": true,
        "typography-w-500": true,
      },
    },
    // ... more component styles
  },
  elements: {
    button: {
      "typography-f-sf": true,
      "layout-pt-3": true,
      "border-br-16": true,
    },
    // ... element styles
  },
};

Full Theme Example

See the complete theme configuration in the A2A starter template

Step 4: Configure Your Agent

Your agent needs to emit A2UI JSON specifications. Here’s an example showing a restaurant list:
agent/prompt_builder.py
RESTAURANT_UI_EXAMPLES = """
---BEGIN SINGLE_COLUMN_LIST_EXAMPLE---
[
  { "beginRendering": { 
      "surfaceId": "default", 
      "root": "root-column" 
  }},
  { "surfaceUpdate": {
    "surfaceId": "default",
    "components": [
      { 
        "id": "root-column", 
        "component": { 
          "Column": { 
            "children": { 
              "explicitList": ["title-heading", "item-list"] 
            } 
          } 
        } 
      },
      { 
        "id": "title-heading", 
        "component": { 
          "Text": { 
            "usageHint": "h1", 
            "text": { "literalString": "Top Restaurants" } 
          } 
        } 
      },
      { 
        "id": "item-list", 
        "component": { 
          "List": { 
            "direction": "vertical", 
            "children": { 
              "template": { 
                "componentId": "item-card", 
                "dataBinding": "/items" 
              } 
            } 
          } 
        } 
      },
      { 
        "id": "item-card", 
        "component": { 
          "Card": { 
            "child": "card-content" 
          } 
        } 
      },
      { 
        "id": "card-content", 
        "component": { 
          "Column": { 
            "children": { 
              "explicitList": ["name-text", "rating-text"] 
            } 
          } 
        } 
      },
      { 
        "id": "name-text", 
        "component": { 
          "Text": { 
            "usageHint": "h3", 
            "text": { "path": "name" } 
          } 
        } 
      },
      { 
        "id": "rating-text", 
        "component": { 
          "Text": { 
            "text": { "path": "rating" } 
          } 
        } 
      }
    ]
  }},
  { "dataModelUpdate": {
    "surfaceId": "default",
    "path": "/",
    "contents": [
      { "key": "items", "valueMap": [
        { "key": "item1", "valueMap": [
          { "key": "name", "valueString": "The Fancy Place" },
          { "key": "rating", "valueNumber": 4.8 }
        ]},
        { "key": "item2", "valueMap": [
          { "key": "name", "valueString": "Quick Bites" },
          { "key": "rating", "valueNumber": 4.2 }
        ]}
      ]}
    ]
  }}
]
---END SINGLE_COLUMN_LIST_EXAMPLE---
"""
The agent prompt includes these examples, teaching the LLM how to generate A2UI specs.

A2UI Component Library

A2UI provides a rich vocabulary of components:

Layout Components

Column

Vertical stack of child components
{
  "id": "my-column",
  "component": {
    "Column": {
      "children": {
        "explicitList": ["child1", "child2"]
      }
    }
  }
}

Row

Horizontal arrangement of components
{
  "id": "my-row",
  "component": {
    "Row": {
      "children": {
        "explicitList": ["left", "right"]
      }
    }
  }
}

List

Repeated items from data binding
{
  "id": "my-list",
  "component": {
    "List": {
      "direction": "vertical",
      "children": {
        "template": {
          "componentId": "item-template",
          "dataBinding": "/items"
        }
      }
    }
  }
}

Display Components

Text

Display text with styling hints
{
  "id": "my-heading",
  "component": {
    "Text": {
      "usageHint": "h1",
      "text": { "literalString": "Welcome" }
    }
  }
}

Image

Display images with URLs
{
  "id": "my-image",
  "component": {
    "Image": {
      "url": { "path": "imageUrl" }
    }
  }
}

Card

Container with styling
{
  "id": "my-card",
  "component": {
    "Card": {
      "child": "card-content"
    }
  }
}

Interactive Components

Button

Clickable button with actions
{
  "id": "submit-button",
  "component": {
    "Button": {
      "child": "button-text",
      "primary": true,
      "action": {
        "name": "submitForm",
        "context": [
          { "key": "formId", "value": { "path": "id" } }
        ]
      }
    }
  }
}

TextField

Text input field
{
  "id": "email-field",
  "component": {
    "TextField": {
      "label": "Email",
      "element": "email-input"
    }
  }
}

Complete Example: Restaurant Finder

Let’s build a complete restaurant finder with A2UI:

The Result

When the agent responds, users see:
  • A heading “Top Restaurants”
  • Cards for each restaurant showing:
    • Restaurant image
    • Name and rating
    • Details and link
    • “Book Now” button

The A2UI Specification

[
  {
    "beginRendering": {
      "surfaceId": "default",
      "root": "root-column"
    }
  },
  {
    "surfaceUpdate": {
      "surfaceId": "default",
      "components": [
        {
          "id": "root-column",
          "component": {
            "Column": {
              "children": {
                "explicitList": ["title", "restaurant-list"]
              }
            }
          }
        },
        {
          "id": "title",
          "component": {
            "Text": {
              "usageHint": "h1",
              "text": { "literalString": "Top Restaurants" }
            }
          }
        },
        {
          "id": "restaurant-list",
          "component": {
            "List": {
              "direction": "vertical",
              "children": {
                "template": {
                  "componentId": "restaurant-card",
                  "dataBinding": "/restaurants"
                }
              }
            }
          }
        },
        {
          "id": "restaurant-card",
          "component": {
            "Card": {
              "child": "card-row"
            }
          }
        },
        {
          "id": "card-row",
          "component": {
            "Row": {
              "children": {
                "explicitList": ["image", "details"]
              }
            }
          }
        },
        {
          "id": "image",
          "component": {
            "Image": {
              "url": { "path": "imageUrl" }
            }
          }
        },
        {
          "id": "details",
          "component": {
            "Column": {
              "children": {
                "explicitList": ["name", "rating", "book-button"]
              }
            }
          }
        },
        {
          "id": "name",
          "component": {
            "Text": {
              "usageHint": "h3",
              "text": { "path": "name" }
            }
          }
        },
        {
          "id": "rating",
          "component": {
            "Text": {
              "text": { "path": "rating" }
            }
          }
        },
        {
          "id": "book-button",
          "component": {
            "Button": {
              "child": "button-text",
              "primary": true,
              "action": {
                "name": "book_restaurant",
                "context": [
                  { "key": "restaurantName", "value": { "path": "name" } }
                ]
              }
            }
          }
        },
        {
          "id": "button-text",
          "component": {
            "Text": {
              "text": { "literalString": "Book Now" }
            }
          }
        }
      ]
    }
  },
  {
    "dataModelUpdate": {
      "surfaceId": "default",
      "path": "/",
      "contents": [
        {
          "key": "restaurants",
          "valueMap": [
            {
              "key": "r1",
              "valueMap": [
                { "key": "name", "valueString": "The Fancy Place" },
                { "key": "rating", "valueNumber": 4.8 },
                { "key": "imageUrl", "valueString": "https://example.com/fancy.jpg" }
              ]
            },
            {
              "key": "r2",
              "valueMap": [
                { "key": "name", "valueString": "Quick Bites" },
                { "key": "rating", "valueNumber": 4.2 },
                { "key": "imageUrl", "valueString": "https://example.com/quick.jpg" }
              ]
            }
          ]
        }
      ]
    }
  }
]

Using the A2UI Composer

Instead of writing JSON by hand, use the A2UI Composer to visually design components:

A2UI Composer

Visual tool for generating A2UI specifications - just design your UI and copy the JSON
The composer lets you:
  1. Drag and drop components
  2. Configure properties visually
  3. Preview the result
  4. Copy the generated JSON

Actions and Interactivity

A2UI buttons can trigger actions that flow back to your agent:
{
  "id": "action-button",
  "component": {
    "Button": {
      "child": "button-text",
      "action": {
        "name": "submitForm",
        "context": [
          { "key": "userId", "value": { "path": "currentUserId" } },
          { "key": "formData", "value": { "path": "formValues" } }
        ]
      }
    }
  }
}
When clicked, the action is sent back to your agent via AG-UI, allowing you to handle it:
@agent.register_for_llm(
    description="Handle form submission"
)
def submit_form(userId: str, formData: dict) -> str:
    # Process the form
    return "Form submitted successfully"

Comparison with Other Approaches

FeatureA2UIStatic AG-UIOpen-ended MCP
SetupMediumMediumLow
FlexibilityMediumLowHigh
Type SafetySchema-levelFull (Zod)None
Cross-platformExcellentReact-onlyWeb-only
Visual consistencyGoodExcellentVariable
LLM learnabilityGoodMediumLow

When to Use A2UI

Best For:

  • Multi-platform applications (web, mobile, desktop)
  • Diverse use cases without building custom components for each
  • Cleaner separation between logic and presentation
  • Balanced flexibility and structure

Not Ideal For:

  • Pixel-perfect brand-critical features (use Static AG-UI)
  • Rapid prototyping with arbitrary HTML (use MCP Apps)
  • Highly custom interactions not in the A2UI vocabulary

Best Practices

1. Provide Clear Component IDs

// Good: Descriptive IDs
{ "id": "user-profile-card", "component": { "Card": {...} } }

// Avoid: Generic IDs
{ "id": "card1", "component": { "Card": {...} } }

2. Use Data Binding for Dynamic Content

// Good: Data binding
{
  "id": "user-name",
  "component": {
    "Text": {
      "text": { "path": "user.name" }
    }
  }
}

// Avoid: Hardcoded values for dynamic data
{
  "id": "user-name",
  "component": {
    "Text": {
      "text": { "literalString": "John Doe" }
    }
  }
}

3. Structure for Reusability

// Good: Reusable template
{
  "id": "user-list",
  "component": {
    "List": {
      "children": {
        "template": {
          "componentId": "user-card",
          "dataBinding": "/users"
        }
      }
    }
  }
}

Next Steps

A2UI Composer

Visual tool for designing A2UI components

A2UI Specification

Full A2UI specification and documentation

A2A Starter Template

Complete example with A2UI configured

Static AG-UI

Compare with React component approach

Build docs developers (and LLMs) love