# A2UI Best Practices: 10 Essential Patterns for Production Agent-to-UI Integration

> Master A2UI best practices for production. Learn 10 essential patterns for efficient agent-to-UI integration, focusing on stream processing, component models, and state separation.

- Repository: [Google/A2UI](https://github.com/google/A2UI)
- Tags: best-practices
- Published: 2026-03-13

---

**A2UI best practices center on using the framework-agnostic `@a2ui/web_core` library for JSON-L stream processing, maintaining an adjacency-list component model for incremental updates, and strictly separating UI structure from application state via JSON-Pointer data binding.**

A2UI (Agent-to-UI) is an open-source protocol from the `google/A2UI` repository that enables AI agents to stream **declarative JSON messages** to client renderers, building native user interfaces dynamically. Following these architectural patterns ensures your implementation remains reactive, secure, and framework-agnostic across React, Lit, or custom renderers.

## Protocol Versioning and Catalog Setup

### Select the Appropriate Specification Version

Start by pinning your implementation to a stable protocol version. **v0.8** is the current production-ready specification and should be used unless you require v0.9’s catalog-aware surface creation features. The v0.9 specification is still in draft and introduces breaking changes to how component catalogs are declared during surface initialization.

Consult the version matrix in [`docs/concepts/overview.md`](https://github.com/google/A2UI/blob/main/docs/concepts/overview.md) and the formal specifications in [`docs/specification/v0.8-a2ui.md`](https://github.com/google/A2UI/blob/main/docs/specification/v0.8-a2ui.md) (stable) and [`docs/specification/v0.9-a2ui.md`](https://github.com/google/A2UI/blob/main/docs/specification/v0.9-a2ui.md) (draft) to determine compatibility with your agent’s output format.

### Register the Component Catalog Before Processing

The **component catalog** declares the set of UI component types your client understands. Load the standard catalog immediately after application initialization to ensure the `MessageProcessor` can validate incoming component definitions.

For React applications, invoke `initializeDefaultCatalog()` once at startup. For custom implementations, extend the registry before calling `processMessages()`:

```tsx
import { ComponentRegistry } from '@a2ui/react';
import MyFancyCard from './components/MyFancyCard';

const registry = ComponentRegistry.getInstance();

registry.register('Card', {
  component: MyFancyCard,   // overrides the default Card
});

```

See the Component Registry section in [`renderers/react/README.md`](https://github.com/google/A2UI/blob/main/renderers/react/README.md) (lines 65-78) for extension patterns.

## Core Architectural Patterns

### Leverage the Framework-Agnostic web_core Library

All web renderers share a single battle-tested core that handles **JSON-L streaming**, schema validation, and state management. Import from `@a2ui/web_core` (or `@a2ui/web_core/v0_9` for draft specification support) regardless of whether you use React, Lit, or Angular.

The core provides essential modules:
- `MessageProcessor`: Parses and validates incoming JSON-L streams
- `SurfaceModel`: Manages the adjacency-list component tree and root references
- `DataModel`: Maintains the separate JSON state tree
- `ComponentModel`: Handles property resolution and expression parsing

Reference the implementation in [`renderers/web_core/src/v0_9/processing/message-processor.ts`](https://github.com/google/A2UI/blob/main/renderers/web_core/src/v0_9/processing/message-processor.ts) and the interface definitions in [`renderers/web_core/src/v0_8/data/model-processor.ts`](https://github.com/google/A2UI/blob/main/renderers/web_core/src/v0_8/data/model-processor.ts).

### Adopt the Adjacency-List Component Model

A2UI represents UI hierarchies as a **flat list** of component objects (`id`, `component`, `properties`) rather than nested trees. This adjacency-list pattern enables **incremental updates**—agents can append, remove, or modify single components without transmitting the entire UI structure on every change.

Store components in a `Map<string, Component>` indexed by ID within each `SurfaceModel`. Resolve parent-child relationships only at render time using the `children` property references. This design rationale is detailed in the Component Structure section of [`docs/concepts/overview.md`](https://github.com/google/A2UI/blob/main/docs/concepts/overview.md) (lines 18-20).

### Decouple UI Structure from Application State

Maintain strict separation between **UI components** and **data state**. UI components reference the data model via **JSON-Pointer** paths (e.g., `/users/0/name`) rather than embedding values directly. Update the data tree independently using `dataModelUpdate` (v0.8) or `updateDataModel` (v0.9) messages.

This stateless approach allows the same UI definition to be reused across different datasets and ensures the renderer updates only when data actually changes. Binding rules are documented in [`docs/concepts/data-binding.md`](https://github.com/google/A2UI/blob/main/docs/concepts/data-binding.md).

## Framework-Specific Implementation

### Implement Two-Context State Management (React)

React renderers should separate **actions** (stable callback references) from **state** (versioned data models) into two distinct contexts. This prevents React re-renders when only action handlers change but the underlying data remains constant.

The pattern is illustrated in the React architecture diagram in [`renderers/react/README.md`](https://github.com/google/A2UI/blob/main/renderers/react/README.md) (lines 84-99), showing how `A2UIProvider` maintains action stability while `SurfaceModel` handles data versioning.

### Inject Base Styles Once and Theme Consistently

Call `injectStyles()` once during application initialization to load A2UI’s base CSS. Then apply a specific theme (e.g., `litTheme` or a custom React theme) by overriding only the necessary CSS variables or classes. Avoid inline style hacks that bypass the theming system.

Theme customization guidance is located in the Theme System section of [`renderers/react/README.md`](https://github.com/google/A2UI/blob/main/renderers/react/README.md) (lines 9-38) and the context provider in [`renderers/react/src/theme/ThemeContext.tsx`](https://github.com/google/A2UI/blob/main/renderers/react/src/theme/ThemeContext.tsx).

## Production Reliability and Security

### Validate Schema Compatibility at Build Time

Run the built-in schema verification tests to ensure your renderer’s TypeScript types match the JSON schema shipped with the specification. This catches mismatched property names or invalid component definitions before production deployment.

Execute the test suites in [`renderers/web_core/src/v0_8/schema/verify-schema.test.ts`](https://github.com/google/A2UI/blob/main/renderers/web_core/src/v0_8/schema/verify-schema.test.ts) and [`renderers/web_core/src/v0_9/schema/verify-schema.test.ts`](https://github.com/google/A2UI/blob/main/renderers/web_core/src/v0_9/schema/verify-schema.test.ts) as part of your CI pipeline.

### Handle Transport Errors and Ordering Guarantees

A2UI assumes **ordered message delivery** (typically via SSE, WebSockets, or A2A). Implement reconnection logic that either replays missed messages or restarts the surface entirely when the connection drops. The transport layer must guarantee that `createSurface` precedes `updateComponents` and that data model updates arrive in sequence.

Required client-side error handling strategies are outlined in [`docs/concepts/data-flow.md`](https://github.com/google/A2UI/blob/main/docs/concepts/data-flow.md).

### Secure the Agent-Client Boundary

Never trust data sent from the agent without validation—the `web_core` library handles schema validation automatically, but you must ensure the client never executes arbitrary code. Keep API keys (e.g., Gemini) out of the client bundle; use a backend proxy to authenticate with agent services, as demonstrated in the Quickstart guide.

Review the Security Notice in [`docs/quickstart.md`](https://github.com/google/A2UI/blob/main/docs/quickstart.md) (lines 22-25) for additional hardening recommendations.

## Practical Implementation Examples

### Basic React Setup (v0.8)

```tsx
// src/App.tsx
import { injectStyles } from '@a2ui/react/styles';
import { A2UIProvider, A2UIRenderer, useA2UI } from '@a2ui/react';
import { initializeDefaultCatalog } from '@a2ui/react';

// One‑time initialization
injectStyles();
initializeDefaultCatalog();

function App() {
  const { processMessages } = useA2UI();

  // Example: fetch A2UI messages from a backend endpoint
  const loadUI = async () => {
    const resp = await fetch('/api/a2ui');
    const msgs = await resp.json();               // server‑to‑client messages
    processMessages(msgs);                         // feed to MessageProcessor
  };

  return (
    <A2UIProvider onAction={(e) => console.log('action', e)}>
      <button onClick={loadUI}>Load UI</button>
      {/* Render the surface named "main" */}
      <A2UIRenderer surfaceId="main" />
    </A2UIProvider>
  );
}

```

Reference: React provider implementation in [`renderers/react/README.md`](https://github.com/google/A2UI/blob/main/renderers/react/README.md) (lines 30-49).

### Framework-Agnostic web_core Usage

```ts
import {
  MessageProcessor,
  SurfaceModel,
} from '@a2ui/web_core/v0_9';

// Create a processor that will own surfaces
const processor = new MessageProcessor();

// Feed a JSONL stream (e.g., from SSE)
processor.processLine('{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/basic_catalog.json"}}');
processor.processLine('{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{ "id":"title","component":"Text","text":"Hello A2UI"}]}}');
processor.processLine('{"version":"v0.9","updateDataModel":{"surfaceId":"main","path":"/","value":{"greeting":"Hello"}}');

// Later, retrieve the rendered model for any UI framework
const surface: SurfaceModel = processor.getSurface('main');
console.log(surface.rootComponentId); // → "title"

```

Reference: [`renderers/web_core/src/v0_9/processing/message-processor.ts`](https://github.com/google/A2UI/blob/main/renderers/web_core/src/v0_9/processing/message-processor.ts).

### Incremental Updates via Adjacency List

```ts
// Initial surface definition
processor.processLine('{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"list","component":"List","children":{"template":{"componentId":"item","dataBinding":"$path/to/items"}}]}');

// Later, add a new item without resending the whole list
processor.processLine('{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"item-3","component":"Text","text":"New Item"}]}');

```

The adjacency-list model lets you **append or replace a single component**; the renderer resolves the new tree automatically without requiring a full state refresh.

## Summary

- Use **v0.8** for production stability or **v0.9** for catalog-aware surface creation.
- Build on **`@a2ui/web_core`** to share JSON-L processing logic across React, Lit, or Angular.
- Maintain **adjacency-list** component structures in `SurfaceModel` to enable efficient incremental updates.
- Separate **UI components** from **data state**, binding values via JSON-Pointer paths.
- Register **component catalogs** before processing messages to ensure validation passes.
- Implement **two-context state management** in React to optimize rendering performance.
- Validate **schema compatibility** using the built-in test suites in `renderers/web_core/src/v0_*/schema/`.
- Handle **ordered transport** with reconnection logic for SSE or WebSocket streams.
- Keep **API keys server-side** and validate all agent inputs through the core schema validator.

## Frequently Asked Questions

### What is the difference between A2UI v0.8 and v0.9?

**v0.8** is the stable, production-ready specification recommended for most applications. **v0.9** is a draft specification that introduces catalog-aware surface creation and renames certain message types (e.g., `updateDataModel` instead of `dataModelUpdate`). Both versions are supported by `@a2ui/web_core`, but v0.8 offers long-term stability while v0.9 is subject to breaking changes.

### Can I use A2UI without React?

Yes. The **`@a2ui/web_core`** library is framework-agnostic and provides `MessageProcessor`, `SurfaceModel`, and schema validation for any JavaScript renderer. The React, Lit, and Angular implementations in the repository all consume this shared core, as documented in [`docs/guides/renderer-development.md`](https://github.com/google/A2UI/blob/main/docs/guides/renderer-development.md).

### How does A2UI handle real-time UI updates efficiently?

A2UI uses an **adjacency-list component model** where the UI hierarchy is stored as a flat `Map<string, Component>` rather than a nested tree. This allows agents to send incremental `updateComponents` messages that add, remove, or modify single components without resending the entire UI structure, minimizing bandwidth and render cycles.

### Where should I store API keys when using A2UI?

Never bundle API keys (such as Gemini API keys) in client-side code. Instead, use a **backend proxy** to handle agent authentication and message streaming, passing only validated A2UI JSON messages to the client. The Quickstart guide at [`docs/quickstart.md`](https://github.com/google/A2UI/blob/main/docs/quickstart.md) demonstrates this security pattern.