Welcome back, future A2UI maestro! In our previous chapters, we learned how to build static, agent-generated user interfaces. We explored various components and understood how an AI agent can declare a UI using JSON. But what’s a beautiful interface without the ability to interact with it? Pretty, but not very useful, right?
In this chapter, we’re going to unlock the true power of A2UI: interactivity. We’ll delve into how agent-driven interfaces handle user actions and manage UI state. This is where your AI agent truly comes alive, responding to user input and dynamically updating the interface. Get ready to make your UIs responsive and engaging, all while maintaining the declarative, secure nature of A2UI.
By the end of this chapter, you’ll understand the fundamental loop of user interaction, agent processing, and UI updates, and you’ll be able to build components that trigger specific actions for your agent to handle.
Core Concepts: A2UI Actions and State
Traditional web development often involves a lot of client-side JavaScript to handle button clicks, form submissions, and dynamic UI updates. A2UI takes a different, agent-centric approach. Remember, the core principle of A2UI is that the agent generates the UI, and the UI never executes arbitrary code. So, how do we make things interactive?
What are A2UI Actions?
In A2UI, when a user interacts with a component (like clicking a button or submitting a form), the UI doesn’t run client-side code. Instead, it sends a declarative message, known as an Action, back to the intelligent agent. Think of an Action as a structured request or notification from the UI to the agent.
Each action typically has:
action_id: A unique identifier for the type of action being performed (e.g., “increment_counter”, “submit_order”, “toggle_light”).payload: An optional JSON object containing any relevant data associated with the action (e.g.,{ "amount": 1 },{ "item_id": "SKU123" },{ "state": "on" }).
The UI component (like a button) is configured to emit a specific action when interacted with. For example, a button might have an on_click property that specifies the action_id and payload to send.
Why this approach? It ensures security and control. The agent remains the single source of truth and logic, preventing malicious or unintended client-side code execution.
What is A2UI State Management?
Since the UI is declarative and doesn’t execute arbitrary code, it also doesn’t directly manage its own complex internal state in the way a React or Angular application might. Instead, the agent is responsible for managing the application’s state.
When the agent receives an action, it processes it, updates its internal understanding of the application’s state, and then generates a new A2UI JSON structure that reflects this updated state. The A2UI renderer then receives this new JSON and efficiently updates the visible user interface.
This means that every UI change, from a simple counter increment to a complex form validation error, is driven by the agent sending a fresh A2UI description.
The Action-State Loop: How Interactivity Flows
Let’s visualize this fundamental cycle that powers all A2UI interactivity. It’s a continuous loop:
- User Interaction: You click a button, type in a field, or select an item.
- UI Sends Action: The A2UI renderer, based on the component’s definition (e.g.,
on_click), packages anaction_idandpayloadinto a JSON message and sends it to the AI agent. - Agent Processes Action: The AI agent receives this action. It interprets the
action_idandpayload, performs necessary logic (e.g., incrementing a counter, fetching data, updating a database). - Agent Updates Internal State: Based on the processing, the agent updates its own internal model of the application’s state.
- Agent Generates New A2UI: With its updated internal state, the agent constructs a new A2UI JSON payload that reflects the desired changes in the user interface.
- UI Renders Updated UI: The A2UI renderer receives this new JSON and updates the visible components, completing the loop.
This loop is the heart of A2UI’s dynamic capabilities. It’s robust, secure, and keeps the agent in full control.
Step-by-Step Implementation: Building an Interactive Counter
Let’s put these concepts into practice by building a simple counter interface. Our agent will initially display “0”, and we’ll add buttons to increment and decrement this number.
Imagine our agent has an internal variable, current_count, initialized to 0.
Step 1: Initial UI - Displaying the Count
First, let’s have our agent generate a simple UI that just shows the current count.
{
"root": {
"type": "container",
"children": [
{
"type": "text",
"text": "Current Count: 0",
"style": {
"font_size": "large",
"padding": "16px"
}
}
]
}
}
Explanation:
- We use a
containerto hold our elements. - Inside, a
textcomponent displays “Current Count: 0”. This0would come from the agent’s internalcurrent_countvariable. styleis used for basic formatting, making the text larger and adding some padding.
If you were to render this, you’d simply see “Current Count: 0”. Now, let’s make it interactive!
Step 2: Adding an Increment Button
We’ll add a button that, when clicked, tells our agent to increment the count.
We’ll introduce the button component and its crucial on_click property.
{
"root": {
"type": "container",
"children": [
{
"type": "text",
"text": "Current Count: 0",
"style": {
"font_size": "large",
"padding": "16px"
}
},
{
"type": "button",
"text": "Increment",
"on_click": {
"action_id": "increment_count",
"payload": {
"value": 1
}
},
"style": {
"margin_top": "8px"
}
}
]
}
}
Explanation of the new button component:
"type": "button": Declares a button component."text": "Increment": This is the label displayed on the button."on_click": { ... }: This is the magic! It defines what happens when the button is clicked."action_id": "increment_count": This is a unique identifier for this specific action. When the user clicks “Increment”, the UI sends a message to the agent saying, “Hey, the user wants toincrement_count!”"payload": { "value": 1 }: This is optional data associated with the action. Here, we’re explicitly telling the agent to increment by1. We could also just send{}and have the agent assume+1.
"style": { "margin_top": "8px" }: Just some styling to give the button space from the text.
When the user clicks this button, the A2UI renderer will bundle { "action_id": "increment_count", "payload": { "value": 1 } } and send it back to your agent. Your agent would then:
- Receive the action.
- Update its internal
current_count(e.g., from 0 to 1). - Generate a new A2UI JSON similar to the above, but with
"text": "Current Count: 1". - Send this new JSON back to the UI, which then updates to show “Current Count: 1”.
Pretty neat, right?
Step 3: Adding a Decrement Button
Now, let’s add a second button to decrement the count. This will follow the exact same pattern, but with a different action_id and payload.
{
"root": {
"type": "container",
"children": [
{
"type": "text",
"text": "Current Count: 0",
"style": {
"font_size": "large",
"padding": "16px"
}
},
{
"type": "button",
"text": "Increment",
"on_click": {
"action_id": "increment_count",
"payload": {
"value": 1
}
},
"style": {
"margin_top": "8px",
"margin_right": "8px"
}
},
{
"type": "button",
"text": "Decrement",
"on_click": {
"action_id": "decrement_count",
"payload": {
"value": 1
}
},
"style": {
"margin_top": "8px"
}
}
]
}
}
Explanation of the new decrement_count button:
- It’s another
buttoncomponent. "text": "Decrement": Its label."on_click": { "action_id": "decrement_count", "payload": { "value": 1 } }: A distinctaction_idfor decrementing. Thepayloadindicates we want to decrement by1.- Notice the
margin_righton the Increment button’s style to give some space between the two buttons.
Now, your A2UI is fully interactive! When the user clicks either button, a specific action is sent to the agent. The agent processes it, updates its internal count, and sends back a new A2UI description with the updated number.
Step 4: Agent-side Logic (Conceptual)
While this chapter focuses on the A2UI JSON, it’s crucial to understand what happens on the agent’s side. Your AI agent, whether it’s a Python script using the Agent Development Kit (ADK) or another framework, would have logic similar to this (pseudocode):
# Pseudocode for Agent's Action Handler
def handle_a2ui_action(action):
global current_count
action_id = action.get("action_id")
payload = action.get("payload", {})
if action_id == "increment_count":
increment_by = payload.get("value", 1)
current_count += increment_by
print(f"Count incremented to: {current_count}")
elif action_id == "decrement_count":
decrement_by = payload.get("value", 1)
current_count -= decrement_by
print(f"Count decremented to: {current_count}")
else:
print(f"Unknown action: {action_id}")
# After processing, generate and return the NEW A2UI JSON
return generate_a2ui_for_current_count(current_count)
def generate_a2ui_for_current_count(count):
return {
"root": {
"type": "container",
"children": [
{
"type": "text",
"text": f"Current Count: {count}", # This text is dynamically generated
"style": {
"font_size": "large",
"padding": "16px"
}
},
{
"type": "button",
"text": "Increment",
"on_click": {
"action_id": "increment_count",
"payload": {
"value": 1
}
},
"style": {
"margin_top": "8px",
"margin_right": "8px"
}
},
{
"type": "button",
"text": "Decrement",
"on_click": {
"action_id": "decrement_count",
"payload": {
"value": 1
}
},
"style": {
"margin_top": "8px"
}
}
]
}
}
current_count = 0 # Agent's internal state
# ... (agent framework would call handle_a2ui_action when an action is received)
This pseudocode illustrates how the agent’s internal state (current_count) is updated, and then a new A2UI JSON is generated with the updated value, completing the interaction loop.
Mini-Challenge: A Simple Toggle Button
Let’s test your understanding of actions and payloads.
Challenge: Create an A2UI interface with a single button. This button should toggle a “light” state. When clicked, it should send an action with action_id: "toggle_light" and a payload that indicates the new desired state (e.g., { "state": "on" } or { "state": "off" }).
Hint: You’ll need to think about how the agent would know the current state to determine the next desired state. For this challenge, just define the action for a single click, perhaps always setting it to “on” for the first click, or assuming the agent will handle the toggling logic based on its internal state. Focus on the button’s on_click definition.
<!-- Your JSON code goes here -->
Need a little nudge? Click for a hint!
Think about how the button's `text` would change if the agent updated its state. For the `payload`, you could send the *current* state from the agent's perspective, or just a generic "toggle" and let the agent decide. For this challenge, let's assume the agent is smart enough to toggle. So, what's the simplest action you can send?Ready for the solution? Click to reveal!
{
"root": {
"type": "container",
"children": [
{
"type": "text",
"text": "Light is currently OFF",
"style": {
"font_size": "medium",
"padding": "12px"
}
},
{
"type": "button",
"text": "Toggle Light",
"on_click": {
"action_id": "toggle_light",
"payload": {}
},
"style": {
"margin_top": "10px"
}
}
]
}
}
What to observe/learn: In this solution, the payload is empty. This is perfectly valid! The agent would receive the toggle_light action and then, based on its own internal state (e.g., a boolean is_light_on), it would switch the state. Then, it would send a new A2UI JSON where the text component would say “Light is currently ON” or “Light is currently OFF” accordingly. This demonstrates how the agent is the brain, and the UI just sends signals.
Common Pitfalls & Troubleshooting
- Expecting Client-Side JavaScript: This is the most common pitfall for developers coming from traditional web backgrounds. Remember, A2UI is declarative. Your UI components define what actions to send, not how to perform client-side logic. If you find yourself thinking, “I need to write a JavaScript function here,” stop and re-evaluate; the logic belongs to the agent.
- Mismatched
action_id: Ensure that theaction_iddefined in your A2UI JSON (on_click.action_id) exactly matches what your agent’s action handling logic is expecting. A typo here will lead to your agent not responding to user input. - Forgetting to Update A2UI from Agent: After an agent processes an action, it must generate and send back a new A2UI JSON payload to the renderer. If the agent processes an action but doesn’t send updated UI, the user interface will remain static, giving the impression that nothing happened. The agent is responsible for all UI updates.
- Overly Complex
payload: Whilepayloadcan contain data, try to keep it concise and relevant. Don’t send the entire application state in every payload. The agent already holds the authoritative state. The payload should only contain information the agent needs to process this specific action.
Summary
You’ve just taken a monumental leap in understanding A2UI! Here’s a quick recap of the key takeaways:
- A2UI Interactivity: It’s driven by a clear Action-State loop.
- Actions are Declarative: User interactions trigger
Actions(JSON messages withaction_idandpayload) sent from the UI to the agent, not client-side code execution. - Agent Manages State: The AI agent is the single source of truth for application state. It processes actions, updates its internal state, and then generates new A2UI JSON.
- UI Re-renders: The A2UI renderer receives the new JSON from the agent and updates the visible interface efficiently.
on_clickProperty: This is how you define actions for interactive components like buttons.
You now have the tools to build truly dynamic and responsive agent-driven interfaces! This understanding is crucial as you move towards more complex applications.
In the next chapter, we’ll explore more advanced input types and how to handle form submissions, allowing your agents to gather richer information from users. Stay curious and keep building!
References
- Introducing A2UI: An open project for agent-driven interfaces - Google Developers Blog (December 2025)
- What is A2UI? - A2UI Official Introduction
- Getting Started with A2UI: Your First Component - A2UI.sh Article (Note: This is an example of an article that would exist given the search results, not an official Google source, but good for practical steps).
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.