---
title: "A2UI — Agent-to-User Interface Protocol"
description: "Reference for the A2UI protocol — let AI agents generate rich, interactive UIs via safe declarative JSON, rendered in real time with React"
canonical: "https://docs.ainative.studio/docs/ai-kit/a2ui"
last-updated: "2026-10-22T17:48:46.689Z"
---

# A2UI — Agent-to-User Interface Protocol

Source: https://docs.ainative.studio/docs/ai-kit/a2ui

> Reference for the A2UI protocol — let AI agents generate rich, interactive UIs via safe declarative JSON, rendered in real time with React

## A2UI Protocol

A2UI (Agent-to-User Interface) is a protocol that lets AI agents describe user interfaces using declarative JSON. The agent never touches the DOM — it generates a component tree spec, your application renders it using pre-approved components from your own catalog.

```bash
npm install @ainative/ai-kit-a2ui-core
npm install @ainative/ai-kit-a2ui   # React renderer
```

:::note
A2UI is currently in **alpha**. The core library has 429 tests at 96% coverage. The React renderer implements 11 of 17 standard components. The remaining 6 are planned.
:::

---

## How it works

```
Agent                         Your App
─────                         ────────
Generates JSON spec    →      A2UIRenderer receives spec
  { type, props,             Maps each node to a real
    children: [...] }   →    React component via registry
                              Renders interactive UI

User interacts         ←     User clicks button, types, etc.
  (WebSocket action)  ←     onAction callback fires
```

1. Your agent responds with a JSON object following the A2UI component schema.
2. Pass that JSON to `<A2UIRenderer>`. It validates the spec and maps each `type` to a component from the registry.
3. The default registry uses shadcn/ui components with full accessibility out of the box.
4. User interactions (button clicks, text input, slider changes) are captured and sent back to the agent via the `onAction` callback or WebSocket transport.
5. The agent updates the spec in response to actions, and the renderer re-renders.

### Security model

Agents generate JSON — not executable code. There is no `eval()`, no `innerHTML` injection, and no XSS surface. The component registry acts as the security boundary: only types listed in the registry can be rendered. Unknown types are silently ignored or surfaced as a debug placeholder in development.

---

## Protocol format

Every A2UI spec is a JSON object with this shape:

```typescript
interface A2UIComponent {
  type: string;                          // Component type from the registry
  props?: Record<string, unknown>;       // Component properties
  children?: A2UIComponent[] | string;   // Child nodes or text content
}
```

A complete spec is a single root `A2UIComponent`. The renderer walks the tree recursively.

### State bindings

Props can reference mutable state using JSON Pointer (RFC 6901). The renderer maintains a state object and resolves pointers at render time:

```json
{
  "type": "TextField",
  "props": {
    "label": "Your name",
    "value": { "$ref": "/state/userName" },
    "onChange": { "$action": "set:/state/userName" }
  }
}
```

When the user types in the field, the renderer writes the new value back to `/state/userName` and re-renders the tree. This makes all components reactive without any custom code.

### Event handlers

Button actions and other events are expressed as strings in the format `verb:target`:

| Pattern | Example | Meaning |
|---------|---------|---------|
| `navigate:/path` | `navigate:/dashboard` | Navigate to a route |
| `set:/state/key` | `set:/state/isOpen` | Write a value to state |
| `emit:eventName` | `emit:formSubmit` | Fire a named event to the `onAction` handler |
| `ws:methodName` | `ws:runAgent` | Send a WebSocket message to the agent |

```json
{
  "type": "Button",
  "props": {
    "label": "Submit",
    "variant": "primary",
    "action": "emit:formSubmit"
  }
}
```

---

## Component reference

### Implemented components (11)

#### Container

Layout wrapper. Controls direction, gap, and padding for its children.

```json
{
  "type": "Container",
  "props": {
    "direction": "column",
    "gap": 16,
    "padding": 24
  },
  "children": []
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `direction` | `'row' \| 'column'` | `'column'` | Flex direction |
| `gap` | `number` | `0` | Gap between children in pixels |
| `padding` | `number` | `0` | Internal padding in pixels |

---

#### Heading

Section heading. Renders the appropriate `<h1>`–`<h6>` element.

```json
{
  "type": "Heading",
  "props": { "level": 2 },
  "children": "Edit Profile"
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Heading level |
| `children` | `string` | required | Heading text |

---

#### Text

Paragraph text with optional visual variants.

```json
{
  "type": "Text",
  "props": { "variant": "muted" },
  "children": "Some descriptive copy here."
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'default' \| 'muted'` | `'default'` | `muted` renders in a subdued color for secondary text |
| `children` | `string` | required | Text content |

---

#### Button

Clickable action button. Fires the `action` string on click.

```json
{
  "type": "Button",
  "props": {
    "label": "Save Changes",
    "variant": "primary",
    "action": "emit:save"
  }
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | `string` | required | Button text |
| `variant` | `'primary' \| 'outline' \| 'ghost'` | `'outline'` | Visual style |
| `action` | `string` | — | Action string to emit on click |
| `disabled` | `boolean` | `false` | Disable the button |

---

#### TextField

Single-line text input with a label.

```json
{
  "type": "TextField",
  "props": {
    "label": "Email",
    "placeholder": "you@example.com",
    "value": "jane@ainative.studio"
  }
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | `string` | — | Label displayed above the input |
| `placeholder` | `string` | — | Placeholder text |
| `value` | `string` | `''` | Current value |
| `type` | `'text' \| 'email' \| 'password' \| 'number'` | `'text'` | Input type |
| `onChange` | `string` | — | Action string or JSON Pointer to update on change |

---

#### CheckBox

Boolean toggle with a label.

```json
{
  "type": "CheckBox",
  "props": {
    "label": "Subscribe to newsletter",
    "checked": true
  }
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | `string` | required | Label text |
| `checked` | `boolean` | `false` | Current checked state |
| `onChange` | `string` | — | Action string or JSON Pointer to update on change |

---

#### Slider

Numeric range slider.

```json
{
  "type": "Slider",
  "props": {
    "label": "Temperature",
    "min": 0,
    "max": 2,
    "value": 0.7,
    "step": 0.1
  }
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | `string` | — | Label displayed above the slider |
| `min` | `number` | `0` | Minimum value |
| `max` | `number` | `100` | Maximum value |
| `value` | `number` | `0` | Current value |
| `step` | `number` | `1` | Step increment |
| `onChange` | `string` | — | Action string or JSON Pointer to update on change |

---

#### Tabs

Horizontal tab bar. Displays the active tab visually; switching tabs fires an action.

```json
{
  "type": "Tabs",
  "props": {
    "tabs": [
      { "id": "overview", "label": "Overview" },
      { "id": "settings", "label": "Settings" }
    ],
    "activeTab": "overview"
  }
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `tabs` | `Array<{ id: string; label: string }>` | required | Tab definitions |
| `activeTab` | `string` | — | ID of the currently active tab |
| `onChange` | `string` | — | Action string fired when a tab is selected |

---

#### List

Vertical list of child components with optional bordered styling.

```json
{
  "type": "List",
  "props": { "variant": "bordered" },
  "children": [
    { "type": "Text", "children": "Item one" },
    { "type": "Text", "children": "Item two" }
  ]
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'default' \| 'bordered'` | `'default'` | `bordered` adds a border and dividers between items |
| `children` | `A2UIComponent[]` | required | List items |

---

#### ChoicePicker

Single-select radio group with labeled options and optional descriptions.

```json
{
  "type": "ChoicePicker",
  "props": {
    "options": [
      { "id": "fast", "label": "Fast", "description": "Lower quality, faster response" },
      { "id": "balanced", "label": "Balanced", "description": "Recommended" },
      { "id": "quality", "label": "Quality", "description": "Best output, slower" }
    ],
    "selected": "balanced"
  }
}
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `options` | `Array<{ id: string; label: string; description?: string }>` | required | Available choices |
| `selected` | `string` | — | ID of the currently selected option |
| `onChange` | `string` | — | Action string or JSON Pointer to update on selection |

---

#### Divider

Horizontal rule used to visually separate sections.

```json
{ "type": "Divider" }
```

No props.

---

### Planned components (6)

The following components are defined in the A2UI protocol specification but are not yet implemented in the React renderer. They are rendered as a debug placeholder in development.

| Type | Description |
|------|-------------|
| `Icon` | Named icon from a configurable icon set |
| `Image` | Image with `src`, `alt`, and optional dimensions |
| `Video` | Video player with controls |
| `AudioPlayer` | Audio player with playback controls |
| `Modal` | Dialog overlay triggered by an action |
| `DateInput` | Date and time picker |

---

## Integration guide

### Basic usage

```tsx
import { A2UIRenderer } from '@ainative/ai-kit-a2ui';

const spec = {
  type: 'Container',
  props: { direction: 'column', gap: 16, padding: 24 },
  children: [
    { type: 'Heading', props: { level: 2 }, children: 'Hello, Agent UI' },
    { type: 'Text', children: 'This entire UI was generated from JSON.' },
    { type: 'Button', props: { label: 'Get Started', variant: 'primary', action: 'emit:start' } },
  ],
};

export function AgentOutput() {
  return (
    <A2UIRenderer
      spec={spec}
      onAction={(action) => {
        console.log('User triggered:', action);
      }}
    />
  );
}
```

### With WebSocket (live agent)

```tsx
import { A2UIRenderer } from '@ainative/ai-kit-a2ui';
import { useCoAgent } from '@ainative/ai-kit-a2ui/react';

export function LiveAgentUI() {
  const { schema, sendAction } = useCoAgent({
    url: 'wss://api.ainative.studio/agent/ws',
    agentId: 'my-agent',
  });

  if (!schema) return <p>Connecting to agent…</p>;

  return (
    <A2UIRenderer
      spec={schema}
      onAction={(action) => sendAction(action)}
    />
  );
}
```

### Custom component registry

Override default components or add your own:

```tsx
import { A2UIRenderer } from '@ainative/ai-kit-a2ui';
import { MyChart } from '@/components/MyChart';

export function CustomAgentUI({ spec }) {
  return (
    <A2UIRenderer
      spec={spec}
      components={{
        // Override a built-in component
        Button: ({ label, variant, onClick }) => (
          <button className={`btn btn-${variant}`} onClick={onClick}>
            {label}
          </button>
        ),
        // Add a custom component type the agent can use
        MetricsChart: MyChart,
      }}
      onAction={(action) => console.log(action)}
    />
  );
}
```

---

## Full JSON examples

### User profile form

```json
{
  "type": "Container",
  "props": { "direction": "column", "gap": 16, "padding": 24 },
  "children": [
    { "type": "Heading", "props": { "level": 2 }, "children": "Edit Profile" },
    {
      "type": "Text",
      "props": { "variant": "muted" },
      "children": "Update your personal information below."
    },
    { "type": "Divider" },
    {
      "type": "TextField",
      "props": {
        "label": "Full Name",
        "placeholder": "John Doe",
        "value": "Jane Smith"
      }
    },
    {
      "type": "TextField",
      "props": {
        "label": "Email",
        "placeholder": "you@example.com",
        "value": "jane@ainative.studio",
        "type": "email"
      }
    },
    {
      "type": "Slider",
      "props": { "label": "Experience Level", "min": 1, "max": 10, "value": 7, "step": 1 }
    },
    {
      "type": "CheckBox",
      "props": { "label": "Subscribe to newsletter", "checked": true }
    },
    {
      "type": "Button",
      "props": { "variant": "primary", "label": "Save Changes", "action": "emit:save" }
    }
  ]
}
```

### Agent status dashboard

```json
{
  "type": "Container",
  "props": { "direction": "column", "gap": 16, "padding": 24 },
  "children": [
    { "type": "Heading", "props": { "level": 2 }, "children": "Agent Status" },
    {
      "type": "Tabs",
      "props": {
        "tabs": [
          { "id": "active", "label": "Active (3)" },
          { "id": "idle", "label": "Idle (2)" },
          { "id": "errors", "label": "Errors (0)" }
        ],
        "activeTab": "active"
      }
    },
    {
      "type": "List",
      "props": { "variant": "bordered" },
      "children": [
        { "type": "Text", "children": "aurora — Processing customer query (45s)" },
        { "type": "Text", "children": "sage — Generating API response (12s)" },
        { "type": "Text", "children": "nova — Running security scan (78s)" }
      ]
    },
    { "type": "Divider" },
    {
      "type": "Container",
      "props": { "direction": "row", "gap": 8 },
      "children": [
        { "type": "Button", "props": { "variant": "primary", "label": "Refresh", "action": "ws:refresh" } },
        { "type": "Button", "props": { "variant": "outline", "label": "View Logs", "action": "navigate:/logs" } }
      ]
    }
  ]
}
```

### Model configuration picker

```json
{
  "type": "Container",
  "props": { "direction": "column", "gap": 16, "padding": 24 },
  "children": [
    { "type": "Heading", "props": { "level": 2 }, "children": "Select AI Model" },
    {
      "type": "Text",
      "props": { "variant": "muted" },
      "children": "Choose the model that best fits your use case."
    },
    {
      "type": "ChoicePicker",
      "props": {
        "options": [
          { "id": "llama-70b", "label": "Llama 3.3 70B", "description": "Best open-source quality" },
          { "id": "llama-8b", "label": "Llama 3.3 8B", "description": "Fast and cost-efficient" },
          { "id": "mistral", "label": "Mistral 7B", "description": "Compact and capable" }
        ],
        "selected": "llama-70b"
      }
    },
    {
      "type": "Slider",
      "props": { "label": "Temperature", "min": 0, "max": 2, "value": 0.7, "step": 0.1 }
    },
    {
      "type": "Slider",
      "props": { "label": "Max Tokens", "min": 100, "max": 4000, "value": 2000, "step": 100 }
    },
    {
      "type": "CheckBox",
      "props": { "label": "Enable streaming", "checked": true }
    },
    {
      "type": "Button",
      "props": { "variant": "primary", "label": "Apply Configuration", "action": "emit:applyConfig" }
    }
  ]
}
```

---

## Packages

| Package | Status | Description |
|---------|--------|-------------|
| `@ainative/ai-kit-a2ui-core` | Alpha | Framework-agnostic core. Protocol types, JSON Pointer (RFC 6901), WebSocket transport, component registry. Zero dependencies. 429 tests, 96% coverage |
| `@ainative/ai-kit-a2ui` | Alpha | React renderer with shadcn/ui component mappings. 11 of 17 components implemented. 70 tests passing |
| `@ainative/ai-kit-nextjs-a2ui` | Planned | Next.js renderer with Server Components, Server Actions, and Streaming SSR |

```bash
## Core (framework-agnostic)
npm install @ainative/ai-kit-a2ui-core

## React renderer
npm install @ainative/ai-kit-a2ui
```

---

## A2UIRenderer props

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `spec` | `A2UIComponent` | required | The root A2UI component spec to render |
| `onAction` | `(action: string) => void` | — | Callback fired when the user triggers any action (button click, etc.) |
| `components` | `Record<string, React.ComponentType>` | — | Override or extend the default component registry |
| `state` | `Record<string, unknown>` | `{}` | Initial state object for JSON Pointer bindings |
| `onStateChange` | `(state: Record<string, unknown>) => void` | — | Callback fired when state is updated by user interaction |

---

## TypeScript types

```typescript
import type {
  A2UIComponent,
  A2UIComponentRegistry,
  A2UIAction,
  A2UIState,
} from '@ainative/ai-kit-a2ui-core';
```

See the source repository at [github.com/AINative-Studio/ai-kit-a2ui](https://github.com/AINative-Studio/ai-kit-a2ui) for full type definitions and the protocol specification.

---

## Further reading

- [Google A2UI specification](https://github.com/google/a2ui)
- [AI Kit React components](/docs/ai-kit/components) — `StreamingMessage`, `CodeBlock`, `StreamingIndicator`
- [React hooks](/docs/ai-kit/hooks) — `useChat`, `useAgent`, `useTask`, `useMemory`
- [AI Kit overview](/docs/ai-kit/overview)
