DEV Community

cz
cz

Posted on

The Complete 2026 Guide: Building Interactive Dashboards with A2UI RizzCharts

🎯 Key Takeaways (TL;DR)

  • RizzCharts is a production-ready sample demonstrating how to build interactive ecommerce dashboards using A2UI and the A2A Protocol
  • Learn to create custom component catalogs with Chart and GoogleMap components beyond the standard A2UI catalog
  • Understand the three-message pattern (beginRendering, surfaceUpdate, dataModelUpdate) for rendering rich UIs
  • Discover how data binding separates UI structure from application state for reactive updates
  • Integrate with Google's Agent Development Kit (ADK) to build AI agents that generate native cross-platform UIs

Table of Contents

  1. What is A2UI RizzCharts?
  2. Why Custom Component Catalogs Matter
  3. Architecture Deep Dive
  4. Implementation Step-by-Step
  5. Custom Components: Chart and GoogleMap
  6. Data Binding and Reactive Updates
  7. Running the RizzCharts Sample
  8. Best Practices
  9. FAQ
  10. Conclusion

What is A2UI RizzCharts?

RizzCharts is an official sample application that showcases how to build an AI-powered ecommerce dashboard using A2UI (Agent to UI) protocol. It demonstrates the power of declarative UI generation where AI agents create rich, interactive visualizations that render natively across platforms.

The sample uses:

  • Google's Agent Development Kit (ADK) for agent orchestration
  • A2A Protocol for agent-to-agent and agent-to-client communication
  • Custom component catalogs extending the standard A2UI components
  • LiteLLM for flexible LLM provider integration (Gemini, OpenAI, etc.)

πŸ’‘ Pro Tip

RizzCharts demonstrates a real-world pattern: AI agents generating domain-specific visualizations (charts, maps) that feel native to the application, not generic chat responses.

Key Capabilities

Feature Description
Sales Charts Interactive doughnut/pie charts showing sales breakdown by product category
Geographic Maps Google Maps integration displaying store locations and performance outliers
Real-time Updates Data-bound components that update reactively when data changes
Custom Catalogs Extended component library beyond standard A2UI components

Why Custom Component Catalogs Matter

The standard A2UI catalog provides common UI elements (Text, Button, TextField, Card, etc.), but real-world applications often need domain-specific components:

  • Stock tickers for financial dashboards
  • Medical charts for healthcare applications
  • CAD viewers for engineering tools
  • Interactive maps for location-based services

How Custom Catalogs Work

graph TD
    A[Client Defines Catalog] --> B[Client Registers Components]
    B --> C[Client Announces Support]
    C --> D[Agent Selects Catalog]
    D --> E[Agent Generates UI]
    E --> F[Client Renders Native Widgets]
Enter fullscreen mode Exit fullscreen mode

The flow:

  1. Client defines catalog β€” Lists both standard and custom components
  2. Client registers implementations β€” Maps component types to native widgets
  3. Client announces support β€” Informs agents which catalogs it supports
  4. Agent selects catalog β€” Chooses appropriate catalog for the UI surface
  5. Agent generates UI β€” Creates surfaceUpdate messages using catalog components
  6. Client renders β€” Displays native widgets without executing arbitrary code

Architecture Deep Dive

Project Structure

samples/agent/adk/rizzcharts/
β”œβ”€β”€ __main__.py                    # Entry point, server setup
β”œβ”€β”€ agent.py                       # RizzchartsAgent with LLM instructions
β”œβ”€β”€ agent_executor.py              # A2A executor with session management
β”œβ”€β”€ component_catalog_builder.py   # Custom catalog loading logic
β”œβ”€β”€ tools.py                       # Data fetching tools
β”œβ”€β”€ rizzcharts_catalog_definition.json  # Custom component schemas
└── examples/
    β”œβ”€β”€ rizzcharts_catalog/        # Examples using custom Chart/GoogleMap
    β”‚   β”œβ”€β”€ chart.json
    β”‚   └── map.json
    └── standard_catalog/          # Fallback using standard components
        β”œβ”€β”€ chart.json
        └── map.json
Enter fullscreen mode Exit fullscreen mode

Core Components

1. RizzchartsAgent (agent.py)

The main agent class that processes user requests and generates A2UI payloads:

class RizzchartsAgent(LlmAgent):
    """An agent that runs an ecommerce dashboard"""

    def __init__(self, model, a2ui_enabled_provider, a2ui_schema_provider):
        super().__init__(
            model=model,
            name="rizzcharts_agent",
            description="An agent that lets sales managers request sales data.",
            instruction=self.get_instructions,
            tools=[
                get_store_sales,      # Fetch regional/store data
                get_sales_data,       # Fetch sales breakdown data
                SendA2uiToClientToolset(...)  # Send A2UI JSON to client
            ],
            planner=BuiltInPlanner(
                thinking_config=types.ThinkingConfig(include_thoughts=True)
            ),
        )
Enter fullscreen mode Exit fullscreen mode

2. Agent Executor (agent_executor.py)

Handles session setup and A2UI extension activation:

class RizzchartsAgentExecutor(A2aAgentExecutor):
    def get_agent_card(self) -> AgentCard:
        return AgentCard(
            name="Ecommerce Dashboard Agent",
            description="Visualizes ecommerce data with charts and maps",
            capabilities=AgentCapabilities(
                streaming=True,
                extensions=[get_a2ui_agent_extension(
                    supported_catalog_ids=[STANDARD_CATALOG_ID, RIZZCHARTS_CATALOG_URI]
                )],
            ),
            skills=[
                AgentSkill(id="view_sales_by_category", ...),
                AgentSkill(id="view_regional_outliers", ...),
            ],
        )
Enter fullscreen mode Exit fullscreen mode

3. Component Catalog Builder (component_catalog_builder.py)

Dynamically loads and merges component schemas:

class ComponentCatalogBuilder:
    def load_a2ui_schema(self, client_ui_capabilities):
        # Check which catalog the client supports
        if RIZZCHARTS_CATALOG_URI in supported_catalog_uris:
            catalog_uri = RIZZCHARTS_CATALOG_URI  # Use custom Chart/GoogleMap
        elif STANDARD_CATALOG_ID in supported_catalog_uris:
            catalog_uri = STANDARD_CATALOG_ID     # Fallback to standard components

        # Merge catalog into A2UI schema
        a2ui_schema_json["properties"]["surfaceUpdate"]
            ["properties"]["components"]["items"]
            ["properties"]["component"]["properties"] = catalog_json

        return a2ui_schema_json, catalog_uri
Enter fullscreen mode Exit fullscreen mode

Implementation Step-by-Step

Step 1: Define Custom Components

Create a JSON schema defining your custom components. Here's the RizzCharts catalog:

{
  "components": {
    "$ref": "standard_catalog_definition.json#/components",
    "Canvas": {
      "type": "object",
      "description": "Renders UI in a stateful panel next to chat",
      "properties": {
        "children": {
          "type": "object",
          "properties": {
            "explicitList": {
              "type": "array",
              "items": { "type": "string" }
            }
          }
        }
      },
      "required": ["children"]
    },
    "Chart": {
      "type": "object",
      "description": "Interactive chart with hierarchical data",
      "properties": {
        "type": {
          "type": "string",
          "enum": ["doughnut", "pie"]
        },
        "title": {
          "type": "object",
          "properties": {
            "literalString": { "type": "string" },
            "path": { "type": "string" }
          }
        },
        "chartData": {
          "type": "object",
          "properties": {
            "literalArray": { "type": "array" },
            "path": { "type": "string" }
          }
        }
      },
      "required": ["type", "chartData"]
    },
    "GoogleMap": {
      "type": "object",
      "description": "Google Map with customizable pins",
      "properties": {
        "center": { "type": "object" },
        "zoom": { "type": "object" },
        "pins": { "type": "object" }
      },
      "required": ["center", "zoom"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Data Fetching Tools

Implement tools that the agent uses to fetch data:

def get_sales_data(time_period: str = "year", **kwargs) -> dict:
    """Gets sales breakdown by product category"""
    return {
        "sales_data": [
            {"label": "Apparel", "value": 41, "drillDown": [
                {"label": "Tops", "value": 31},
                {"label": "Bottoms", "value": 38},
                {"label": "Outerwear", "value": 20},
            ]},
            {"label": "Electronics", "value": 28, "drillDown": [...]},
            {"label": "Home Goods", "value": 15},
            {"label": "Health & Beauty", "value": 10},
            {"label": "Other", "value": 6},
        ]
    }

def get_store_sales(region: str = "all", **kwargs) -> dict:
    """Gets store locations with sales performance"""
    return {
        "center": {"lat": 34, "lng": -118.2437},
        "zoom": 10,
        "locations": [
            {
                "lat": 34.0195, "lng": -118.4912,
                "name": "Santa Monica Branch",
                "description": "High traffic coastal location",
                "outlier_reason": "Yes, 15% sales over baseline",
                "background": "#4285F4",  # Highlighted pin
            },
            {"lat": 34.0488, "lng": -118.2518, "name": "Downtown Flagship"},
            # ... more locations
        ],
    }
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure Agent Instructions

The agent receives detailed instructions for generating A2UI payloads:

def get_instructions(self, readonly_context: ReadonlyContext) -> str:
    return f"""
    ### System Instructions

    You are an expert A2UI Ecommerce Dashboard analyst. Your primary function 
    is to translate user requests into A2UI JSON payloads.

    **Workflow:**
    1. Analyze the Request - Determine intent (Chart vs. Map)
    2. Fetch Data - Use `get_sales_data` or `get_store_sales`
    3. Select Template - Use CHART or MAP example as base
    4. Construct JSON Payload - Generate unique surfaceId, update title
    5. Call Tool - Use `send_a2ui_json_to_client`

    **Examples:**
    - "show sales breakdown by category for Q3" β†’ Chart
    - "were there any outlier stores" β†’ Map

    ---BEGIN CHART EXAMPLE---
    {json.dumps(chart_example)}
    ---END CHART EXAMPLE---

    ---BEGIN MAP EXAMPLE---
    {json.dumps(map_example)}
    ---END MAP EXAMPLE---
    """
Enter fullscreen mode Exit fullscreen mode

Step 4: Create A2UI Message Payloads

A complete A2UI payload consists of three messages:

Chart Example

[
  {
    "beginRendering": {
      "surfaceId": "sales-dashboard",
      "root": "root-canvas"
    }
  },
  {
    "surfaceUpdate": {
      "surfaceId": "sales-dashboard",
      "components": [
        {
          "id": "root-canvas",
          "component": {
            "Canvas": {
              "children": { "explicitList": ["chart-container"] }
            }
          }
        },
        {
          "id": "chart-container",
          "component": {
            "Column": {
              "children": { "explicitList": ["sales-chart"] },
              "alignment": "center"
            }
          }
        },
        {
          "id": "sales-chart",
          "component": {
            "Chart": {
              "type": "doughnut",
              "title": { "path": "chart.title" },
              "chartData": { "path": "chart.items" }
            }
          }
        }
      ]
    }
  },
  {
    "dataModelUpdate": {
      "surfaceId": "sales-dashboard",
      "path": "/",
      "contents": [
        { "key": "chart.title", "valueString": "Sales by Category" },
        { "key": "chart.items[0].label", "valueString": "Apparel" },
        { "key": "chart.items[0].value", "valueNumber": 41 },
        { "key": "chart.items[0].drillDown[0].label", "valueString": "Tops" },
        { "key": "chart.items[0].drillDown[0].value", "valueNumber": 31 }
        // ... more data
      ]
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

Map Example

[
  {
    "beginRendering": {
      "surfaceId": "la-map-view",
      "root": "root-canvas"
    }
  },
  {
    "surfaceUpdate": {
      "surfaceId": "la-map-view",
      "components": [
        {
          "id": "root-canvas",
          "component": {
            "Canvas": { "children": { "explicitList": ["map-layout-container"] } }
          }
        },
        {
          "id": "map-header",
          "component": {
            "Text": {
              "text": { "literalString": "Points of Interest in Los Angeles" },
              "usageHint": "h2"
            }
          }
        },
        {
          "id": "location-map",
          "component": {
            "GoogleMap": {
              "center": { "path": "mapConfig.center" },
              "zoom": { "path": "mapConfig.zoom" },
              "pins": { "path": "mapConfig.locations" }
            }
          }
        }
      ]
    }
  },
  {
    "dataModelUpdate": {
      "surfaceId": "la-map-view",
      "path": "/",
      "contents": [
        { "key": "mapConfig.center.lat", "valueNumber": 34.0522 },
        { "key": "mapConfig.center.lng", "valueNumber": -118.2437 },
        { "key": "mapConfig.zoom", "valueNumber": 11 },
        { "key": "mapConfig.locations[0].lat", "valueNumber": 34.0135 },
        { "key": "mapConfig.locations[0].name", "valueString": "Google Store Santa Monica" }
        // ... more locations
      ]
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

Custom Components: Chart and GoogleMap

Chart Component

The Chart component renders interactive doughnut or pie charts:

Property Type Description
type "doughnut" \ "pie"
title {literalString} \ {path}
chartData {literalArray} \ {path}

DrillDown Support: Each chart item can have nested drillDown data for hierarchical visualization:

{
  "label": "Apparel",
  "value": 41,
  "drillDown": [
    { "label": "Tops", "value": 31 },
    { "label": "Bottoms", "value": 38 },
    { "label": "Outerwear", "value": 20 }
  ]
}
Enter fullscreen mode Exit fullscreen mode

GoogleMap Component

The GoogleMap component displays an interactive map with customizable pins:

Property Type Description
center {lat, lng} Map center coordinates
zoom number Zoom level (1-20)
pins array Array of pin objects

Pin Properties:

{
  "lat": 34.0195,
  "lng": -118.4912,
  "name": "Santa Monica Branch",
  "description": "High traffic coastal location",
  "background": "#4285F4",    // Pin background color
  "borderColor": "#FFFFFF",   // Pin border color
  "glyphColor": "#FFFFFF"     // Pin icon color
}
Enter fullscreen mode Exit fullscreen mode

Data Binding and Reactive Updates

A2UI separates UI structure from application state through data binding:

Literal vs. Path Values

// Literal (fixed value)
{"text": {"literalString": "Sales Dashboard"}}

// Path (data-bound, reactive)
{"text": {"path": "chart.title"}}
Enter fullscreen mode Exit fullscreen mode

When the data at chart.title changes, the component automatically updatesβ€”no component regeneration needed.

JSON Pointer Paths

A2UI uses RFC 6901 JSON Pointer syntax:

Path Resolves To
/user/name Object property
/items/0 First array element
/items/0/price Nested property

Scoped Paths in Templates

When using templates for dynamic lists, paths are scoped to each array item:

{
  "id": "location-name",
  "component": {
    "Text": {
      "text": { "path": "name" }  // Relative to current item
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

For /mapConfig.locations/0, the path name resolves to /mapConfig.locations/0/name.


Running the RizzCharts Sample

Prerequisites

  • Python 3.9+
  • UV package manager
  • LLM API key (Gemini, OpenAI, etc.)

Setup

# Navigate to sample directory
cd samples/agent/adk/rizzcharts

# Create environment file
cp .env.example .env
# Edit .env with your API key

# Run the agent server
uv run .
Enter fullscreen mode Exit fullscreen mode

The server starts on http://localhost:10002 by default.

Environment Variables

Variable Description Default
GEMINI_API_KEY Google AI API key Required
GOOGLE_GENAI_USE_VERTEXAI Use Vertex AI instead FALSE
LITELLM_MODEL LLM model identifier gemini/gemini-2.5-flash

Testing with Example Queries

Once running, send requests like:

  • "Show my sales breakdown by product category for Q3" β†’ Generates doughnut chart
  • "Were there any outlier stores in the region?" β†’ Generates map with highlighted pins
  • "What's the revenue trend year over year?" β†’ Generates chart visualization

Best Practices

1. Descriptive Component IDs

// βœ… Good
{"id": "sales-chart-q3-2026"}
{"id": "store-location-map"}

// ❌ Bad
{"id": "c1"}
{"id": "component"}
Enter fullscreen mode Exit fullscreen mode

2. Separate Structure from Data

Use data bindings for dynamic content:

// βœ… Recommended - Data-bound
{"title": {"path": "chart.title"}}

// ⚠️ Use sparingly - Literal values
{"title": {"literalString": "Static Title"}}
Enter fullscreen mode Exit fullscreen mode

3. Generate Unique Surface IDs

Each request should generate a unique surfaceId:

surface_id = f"sales_breakdown_{time_period}_{uuid.uuid4().hex[:8]}"
Enter fullscreen mode Exit fullscreen mode

4. Validate Against Schema

Always validate generated JSON against the A2UI schema:

jsonschema.validate(instance=example_json, schema=a2ui_schema)
Enter fullscreen mode Exit fullscreen mode

5. Security Considerations

⚠️ Important Security Note

  • Treat all agent-generated content as untrusted input
  • Implement input sanitization for all property values
  • Use Content Security Policies (CSP) in client renderers
  • Validate data strictly before rendering

FAQ

Q: What is the difference between standard and custom catalogs?

A: The standard catalog includes common UI components (Text, Button, Card, List, etc.) that work across all A2UI clients. Custom catalogs extend this with domain-specific components (Chart, GoogleMap, StockTicker) that require client-side implementation. RizzCharts demonstrates both approaches with fallback support.

Q: How does A2UI compare to sending HTML/iframes?

A: A2UI is declarative data, not code. The client renders components using its own native widgets, ensuring:

  • No code execution risk (security)
  • Native look and feel (UX)
  • Consistent styling (design)
  • Cross-platform support (portability)

Q: Can I use RizzCharts components with other protocols?

A: Yes! While RizzCharts uses A2A Protocol, the A2UI message format works with any transport: SSE, WebSockets, AG UI, or direct HTTP. The AP2 Protocol also integrates with A2UI for payment-enabled agent interfaces.

Q: How do I implement custom components on the client?

A: Register component implementations in your client framework:

  1. Define the component schema in your catalog JSON
  2. Implement the rendering logic (Lit, Angular, React, Flutter)
  3. Register the catalog with your A2UI client
  4. Announce supported catalogs to agents

See the Lit samples for reference implementations.

Q: What happens if the client doesn't support a catalog?

A: RizzCharts includes fallback support. The ComponentCatalogBuilder checks client capabilities:

if RIZZCHARTS_CATALOG_URI in supported_catalog_uris:
    catalog_uri = RIZZCHARTS_CATALOG_URI  # Custom components
elif STANDARD_CATALOG_ID in supported_catalog_uris:
    catalog_uri = STANDARD_CATALOG_ID     # Standard components
Enter fullscreen mode Exit fullscreen mode

The standard catalog examples use List and Card components to display the same data without Chart/GoogleMap.


Conclusion

The RizzCharts sample demonstrates the full power of A2UI for building agent-driven dashboards:

  1. Custom component catalogs extend A2UI beyond basic UI elements
  2. Data binding enables reactive, efficient updates
  3. Schema validation ensures type-safe agent outputs
  4. Fallback support provides graceful degradation
  5. Security by design keeps clients in control

Next Steps


Last updated: January 2026

Keywords: A2UI, Agent to UI, RizzCharts, custom components, declarative UI, A2A Protocol, ecommerce dashboard, data visualization, AI agents, LLM UI generation

Top comments (0)