A2UI Declarative Agent Interfaces

Protocols & Interoperability intermediate Building with AI ~30 min read

Overview

A2UI is a declarative format for agent-generated user interfaces. Instead of asking an agent to return plain text, HTML, or executable JavaScript, the agent returns structured component descriptions that a trusted client renderer maps onto native widgets. The result is a UI message that can cross process, framework, and organizational boundaries while still respecting the host application’s styling and security model.

The core problem is that remote agents often know what interaction would help the user, but they should not be allowed to directly mutate the browser DOM or ship arbitrary code into the host application. A restaurant-booking agent may need a date picker, a table of available times, and a submit action. A support agent may need an approval dashboard. A data agent may need a chart plus filters. A2UI gives those agents a portable way to describe the interface without owning the renderer.

This topic focuses on the reusable engineering pattern behind A2UI: component catalogs, schema-validated props, incremental surfaces, trusted renderers, and transport separation. The same pattern applies whether the payload travels over A2A, AG-UI, MCP-adjacent resources, or a custom application channel.

Core Concepts

Declarative UI as Data

A2UI payloads describe what should be rendered, not how to execute rendering code. The agent emits component names, stable IDs, props, layout relationships, and action bindings. The client keeps the actual component implementations. This is the same safety move that made JSON APIs easier to secure than server-sent script: the remote party can request an allowed shape, but the local application decides how that shape becomes UI.

Trusted Component Catalogs

The host application advertises or configures a catalog of components the agent is allowed to use. A catalog entry combines a component name, a schema for its props, and a natural-language description that helps the model choose it correctly. Common catalogs include text, card, image, button, form, table, and chart primitives, while production applications usually add domain-specific components such as order summaries, reservation pickers, approval panels, or metric cards.

Transport and Rendering Are Separate

A2UI is the interface payload. AG-UI, A2A, or another protocol can carry that payload. React, Lit, Angular, Flutter, SwiftUI, or a server-rendered shell can render it. Keeping the transport separate from the renderer prevents the UI format from becoming locked to one agent framework or one frontend stack.

Key Concept: Agents Speak UI, Clients Own UI

The agent chooses a task-appropriate interface from a constrained vocabulary, but the client owns styling, accessibility, event handling, authentication, and execution. That split lets remote agents produce rich interactions without becoming trusted frontend code.

How It Works

From Agent Intent to Rendered Surface

A typical A2UI flow starts when the user asks an agent to perform a task. The host application includes catalog descriptions in the agent context or injects a rendering tool. The agent returns an A2UI surface made of components and IDs. The client validates the payload against the catalog, maps each abstract component to a local renderer, and displays the result. User actions then travel back through the application as events or follow-up messages.

// TypeScript-style sketch: validate and render an A2UI surface.
type ComponentNode = {
  id: string;
  type: string;
  props: Record<string, unknown>;
  children?: string[];
};

type Surface = {
  surfaceId: string;
  nodes: ComponentNode[];
  root: string;
};

function renderSurface(surface: Surface, catalog: ComponentCatalog) {
  const byId = new Map(surface.nodes.map((node) => [node.id, node]));
  return renderNode(surface.root);

  function renderNode(id: string): React.ReactNode {
    const node = byId.get(id);
    if (!node) throw new Error(`Unknown A2UI node: ${id}`);

    const entry = catalog[node.type];
    if (!entry) throw new Error(`Component not allowed: ${node.type}`);

    const props = entry.schema.parse(node.props);
    const children = (node.children ?? []).map(renderNode);
    return entry.render(props, children);
  }
}

Incremental Updates

A2UI is designed for agent conversations, where the best interface may evolve as the user provides more information. Stable component IDs allow the agent to update a specific field, replace a table row, append a result card, or mark an action as completed without resending an entire application. This supports progressive rendering while long-running tools or remote agents continue working.

Framework Integration

Frameworks such as CopilotKit can inject an A2UI tool into the backend runtime and enable an A2UI renderer in the frontend provider. In that arrangement, the application developer still defines custom component schemas and renderers, but the framework handles the message plumbing, streaming, and conversion between agent events and rendered surfaces.

Practical Applications

Workflow Agents

Enterprise workflow agents often need more than a text answer. An onboarding agent may need an approval checklist, a permissions form, a device-selection card, and a status timeline. A2UI lets the agent generate the exact interface for the current workflow state while the host application enforces its own design system and authorization rules.

Cross-Agent Interfaces

In an A2A mesh, the agent that knows how to solve a subtask may be remote. Without a declarative UI format, that remote agent can only return text or an opaque iframe-like artifact. With A2UI, it can return a component blueprint that the orchestrator and client can inspect, style, and render natively.

AI-Native Product Surfaces

Applications that embed agents into dashboards, CRM tools, developer portals, or mobile apps can use A2UI to avoid hard-coding every possible agent response UI. The product team defines safe primitives once, then agents compose them dynamically for booking, reporting, review, data-entry, troubleshooting, or analysis tasks.

Implementation Patterns

Define Schemas Beside Renderers

Every component exposed to an agent should have a runtime schema. The schema protects the renderer from malformed model output and gives the model a clear contract for when to use the component. Keep descriptions short and operational: what the component is for, required props, optional props, and action semantics.

import { z } from "zod";

const reservationPicker = {
  name: "ReservationPicker",
  description: "Choose one available reservation slot for a party.",
  schema: z.object({
    partySize: z.number().int().min(1).max(12),
    options: z.array(z.object({
      id: z.string(),
      label: z.string(),
      startsAt: z.string(),
    })),
  }),
  render: ReservationPicker,
};

Use Stable IDs for Diffable State

Require the agent to assign stable IDs to surfaces and components. Stable IDs make updates deterministic, simplify telemetry, and let the client reconcile UI changes without losing local state such as focus, validation errors, or partially entered form values.

Route Actions Through the Host

A declarative button should not directly call a remote service chosen by the model. Instead, user actions should go through host-owned handlers. The host can check authorization, attach CSRF protections, validate payloads, log the action, and decide whether to send a follow-up message to the agent.

Performance Considerations

Common Pitfalls

Further Reading