diff --git a/apps/website/content/docs/chat/a2ui/catalog.mdx b/apps/website/content/docs/chat/a2ui/catalog.mdx
new file mode 100644
index 000000000..3a07571c2
--- /dev/null
+++ b/apps/website/content/docs/chat/a2ui/catalog.mdx
@@ -0,0 +1,281 @@
+# Component Catalog
+
+The built-in A2UI catalog provides 12 Angular components covering display, layout, and interactive controls. It is included automatically when using `ChatComponent`, and can be instantiated directly for custom rendering setups.
+
+**Import:**
+
+```typescript
+import { a2uiBasicCatalog } from '@cacheplane/chat';
+```
+
+## Signature
+
+```typescript
+function a2uiBasicCatalog(): ViewRegistry
+```
+
+Returns a `ViewRegistry` mapping 12 A2UI type names to their Angular component implementations. Pass the result to `A2uiSurfaceComponent` or use it as a starting point for a custom registry.
+
+```typescript
+const catalog = a2uiBasicCatalog();
+```
+
+## Display Components
+
+### Text
+
+Renders a span of text.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `Text` | `A2uiTextComponent` | `a2ui-text` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `text` | `string` | The text content to display |
+
+```json
+{"id": "greeting", "component": "Text", "text": "Hello, world!"}
+```
+
+### Image
+
+Renders an `
` element.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `Image` | `A2uiImageComponent` | `a2ui-image` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `url` | `string` | Image source URL |
+| `alt` | `string` | Alt text for accessibility |
+
+### Icon
+
+Renders an icon glyph as a text span. The `name` prop is displayed directly — use an emoji or a Unicode symbol.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `Icon` | `A2uiIconComponent` | `a2ui-icon` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `name` | `string` | Glyph to render (e.g., `"✓"`, `"⚠️"`) |
+
+### Divider
+
+Renders a horizontal rule. Has no props.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `Divider` | `A2uiDividerComponent` | `a2ui-divider` |
+
+## Layout Components
+
+Layout components receive `childKeys` — an array of component IDs — and render each child via json-render's `RenderElementComponent`. They also receive the full `spec` so that child elements can be looked up by key.
+
+### Row
+
+Arranges children horizontally with a flex row layout.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `Row` | `A2uiRowComponent` | `a2ui-row` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `childKeys` | `string[]` | Ordered list of child component IDs |
+| `spec` | `Spec` | Injected automatically by the render engine |
+
+### Column
+
+Arranges children vertically with a flex column layout.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `Column` | `A2uiColumnComponent` | `a2ui-column` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `childKeys` | `string[]` | Ordered list of child component IDs |
+| `spec` | `Spec` | Injected automatically by the render engine |
+
+### Card
+
+Renders children inside a rounded bordered card container with an optional title.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `Card` | `A2uiCardComponent` | `a2ui-card` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `title` | `string` | Optional card heading |
+| `childKeys` | `string[]` | Ordered list of child component IDs |
+| `spec` | `Spec` | Injected automatically by the render engine |
+
+### List
+
+Renders children in a scrollable vertical list (max height 24rem).
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `List` | `A2uiListComponent` | `a2ui-list` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `childKeys` | `string[]` | Ordered list of child component IDs |
+| `spec` | `Spec` | Injected automatically by the render engine |
+
+
+For data-driven lists, use the `A2uiChildTemplate` form instead of static `childKeys`. Set `children` to `{"path": "/items", "componentId": "item-template"}` and the surface component will expand the template once per array item. See the [Surface Component](/docs/chat/a2ui/surface-component) page for details.
+
+
+## Interactive Components
+
+Interactive components support **two-way data binding** and **button actions**. They use two special props:
+
+- `_bindings` — a `Record` mapping prop names to JSON Pointer paths in the data model. When the user changes the value, the component emits an `a2ui:datamodel:` event to update the model.
+- `emit` — injected by the render engine; components call it to dispatch events back to the chat.
+
+### Button
+
+Renders a button that dispatches an action when clicked.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `Button` | `A2uiButtonComponent` | `a2ui-button` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `label` | `string` | Button label text |
+| `variant` | `'primary' \| 'borderless'` | Visual style. Defaults to `'primary'` |
+| `disabled` | `boolean` | Disables the button when `true` |
+| `action` | `A2uiAction` | Action to execute on click (event or function call) |
+| `checks` | `A2uiCheck[]` | Validation checks — button is disabled if any fail |
+| `emit` | injected | Event emitter provided by the render engine |
+
+**Action types:**
+
+```json
+// Emit a named event (sent back to the agent)
+{"action": {"event": {"name": "submit", "context": {"formId": "contact"}}}}
+
+// Execute a local function (e.g., open a URL)
+{"action": {"functionCall": {"call": "openUrl", "args": {"url": "https://example.com"}}}}
+```
+
+**Validation checks** reference built-in validators by name. If any check fails, the button is automatically disabled:
+
+```json
+{"checks": [{"call": "required", "args": {"value": {"path": "/email"}}, "message": "Email is required"}]}
+```
+
+### TextField
+
+A single-line text input with optional label and placeholder.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `TextField` | `A2uiTextFieldComponent` | `a2ui-text-field` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `label` | `string` | Input label |
+| `value` | `string` | Current value (bind via `_bindings`) |
+| `placeholder` | `string` | Placeholder text |
+| `_bindings` | `Record` | Bind `value` to a data model path |
+| `emit` | injected | Event emitter provided by the render engine |
+
+```json
+{
+ "id": "name-field",
+ "component": "TextField",
+ "label": "Your name",
+ "value": {"path": "/name"},
+ "_bindings": {"value": "/name"}
+}
+```
+
+### CheckBox
+
+A labeled checkbox with two-way binding for its checked state.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `CheckBox` | `A2uiCheckBoxComponent` | `a2ui-check-box` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `label` | `string` | Checkbox label |
+| `checked` | `boolean` | Current checked state (bind via `_bindings`) |
+| `_bindings` | `Record` | Bind `checked` to a data model path |
+| `emit` | injected | Event emitter provided by the render engine |
+
+### ChoicePicker
+
+A dropdown select control with a list of string options.
+
+| A2UI type | Angular component | Selector |
+|-----------|-------------------|----------|
+| `ChoicePicker` | `A2uiChoicePickerComponent` | `a2ui-choice-picker` |
+
+| Prop | Type | Description |
+|------|------|-------------|
+| `label` | `string` | Select label |
+| `options` | `string[]` | List of available options |
+| `selected` | `string` | Currently selected value (bind via `_bindings`) |
+| `_bindings` | `Record` | Bind `selected` to a data model path |
+| `emit` | injected | Event emitter provided by the render engine |
+
+## Built-in Functions
+
+Props that use `A2uiFunctionCall` can reference these built-in functions:
+
+| Function | Args | Description |
+|----------|------|-------------|
+| `formatNumber` | `value`, `precision`, `grouping` | Formats a number with optional decimal places and thousands grouping |
+| `formatCurrency` | `value`, `locale`, `currency` | Formats a number as a currency string (default: `en-US` / `USD`) |
+| `formatDate` | `value`, `locale` | Formats an ISO date string using `toLocaleDateString` |
+| `pluralize` | `count`, `singular`, `plural` | Returns singular or plural form based on count |
+| `openUrl` | `url` | Opens a URL in a new tab (also usable as a button action) |
+| `and` | any named args | Returns `true` if all arg values are truthy |
+| `or` | any named args | Returns `true` if any arg value is truthy |
+| `not` | `value` | Negates a boolean value |
+
+## Component Summary
+
+| A2UI Type | Angular Component | Category |
+|-----------|-------------------|----------|
+| `Text` | `A2uiTextComponent` | Display |
+| `Image` | `A2uiImageComponent` | Display |
+| `Icon` | `A2uiIconComponent` | Display |
+| `Divider` | `A2uiDividerComponent` | Display |
+| `Row` | `A2uiRowComponent` | Layout |
+| `Column` | `A2uiColumnComponent` | Layout |
+| `Card` | `A2uiCardComponent` | Layout |
+| `List` | `A2uiListComponent` | Layout |
+| `Button` | `A2uiButtonComponent` | Interactive |
+| `TextField` | `A2uiTextFieldComponent` | Interactive |
+| `CheckBox` | `A2uiCheckBoxComponent` | Interactive |
+| `ChoicePicker` | `A2uiChoicePickerComponent` | Interactive |
+
+## What's Next
+
+
+
+ How the A2UI protocol works end-to-end.
+
+
+ Render surfaces and understand dynamic value resolution.
+
+
diff --git a/apps/website/content/docs/chat/a2ui/overview.mdx b/apps/website/content/docs/chat/a2ui/overview.mdx
new file mode 100644
index 000000000..0292dd3a3
--- /dev/null
+++ b/apps/website/content/docs/chat/a2ui/overview.mdx
@@ -0,0 +1,147 @@
+# A2UI Overview
+
+A2UI is an open standard for agent-driven user interfaces. It lets an AI agent describe a structured UI — components, layout, and live data — using a simple JSON protocol, and have that UI rendered automatically inside a chat session.
+
+The `ChatComponent` includes built-in A2UI support with no extra configuration. When an agent response begins with the `---a2ui_JSON---` sentinel, the chat switches to A2UI rendering mode automatically.
+
+
+The implementation follows the A2UI v0.9 specification. For the full protocol reference, see [a2ui.org](https://a2ui.org).
+
+
+## How It Works in ChatComponent
+
+When content arrives with the `---a2ui_JSON---` prefix, the streaming pipeline switches to JSONL mode. Each newline-delimited JSON object is parsed as an A2UI message and handed to the surface store.
+
+```
+AI response starts with ---a2ui_JSON---
+ → ContentClassifier sets type = 'a2ui'
+ → createA2uiMessageParser() parses JSONL line-by-line
+ → Each A2uiMessage is applied to A2uiSurfaceStore
+ → Surfaces signal updates reactively
+ → A2uiSurfaceComponent renders each surface via json-render
+```
+
+The surface store maintains a `Map` keyed by surface ID. Each surface holds a flat component map, a data model, theme metadata, and the catalog ID used to resolve components.
+
+The `ChatComponent` provides the built-in `a2uiBasicCatalog` automatically — you do not need to configure a catalog to start using A2UI.
+
+## The Four Message Types
+
+The A2UI protocol is built on four message types. Agents compose entire interfaces by sending sequences of these messages.
+
+| Message | Purpose |
+|---------|---------|
+| `createSurface` | Creates a new named surface with a catalog ID and optional theme |
+| `updateComponents` | Adds or replaces components on a surface (merged by component ID) |
+| `updateDataModel` | Sets or patches a value in the surface data model via JSON Pointer |
+| `deleteSurface` | Removes a surface and all its components |
+
+### createSurface
+
+Declares a new surface. Must be sent before any other message for that surface ID.
+
+```json
+{"createSurface": {"surfaceId": "order-status", "catalogId": "basic"}}
+```
+
+### updateComponents
+
+Sends one or more component definitions. Each component has a unique `id`, a `component` type name, and any component-specific props.
+
+```json
+{"updateComponents": {
+ "surfaceId": "order-status",
+ "components": [
+ {"id": "root", "component": "Column", "children": ["title", "status"]},
+ {"id": "title", "component": "Text", "text": "Your Order"},
+ {"id": "status", "component": "Text", "text": {"path": "/state"}}
+ ]
+}}
+```
+
+### updateDataModel
+
+Sets values at a JSON Pointer path in the surface's data model. Components whose props reference these paths re-render automatically.
+
+```json
+{"updateDataModel": {"surfaceId": "order-status", "path": "/state", "value": "Shipped"}}
+```
+
+Omit `path` (or use `/`) to replace the entire data model at once.
+
+### deleteSurface
+
+Removes a surface from the store and dismisses its rendered output.
+
+```json
+{"deleteSurface": {"surfaceId": "order-status"}}
+```
+
+## A2UI vs json-render
+
+Both A2UI and json-render produce rendered UIs inside chat messages, but they serve different purposes.
+
+| | A2UI | json-render |
+|-|------|-------------|
+| Protocol | Multi-message JSONL stream | Single JSON object |
+| State | Surfaces with live data models | Stateless spec |
+| Interactivity | Two-way bindings, button actions, validation | Read-only display |
+| Detection | `---a2ui_JSON---` prefix | First character is `{` |
+| Use case | Agent-driven dynamic UIs | Rich content cards |
+
+Use **A2UI** when the agent needs to build an interface incrementally, bind it to live data, or respond to user interactions. Use **json-render** for rich one-shot content cards — formatted results, structured data displays, and similar read-only output.
+
+## Quick Setup
+
+No configuration is required. Add `ChatComponent` to your template and A2UI content is detected and rendered automatically:
+
+```typescript
+import { ChatComponent } from '@cacheplane/chat';
+
+@Component({
+ template: ``,
+ imports: [ChatComponent],
+})
+export class AppComponent {
+ stream = /* your AI stream */;
+}
+```
+
+When the AI response starts with `---a2ui_JSON---`, the chat renders the surfaces using the built-in `a2uiBasicCatalog`. No inputs to set, no catalog to register.
+
+
+To use your own components or extend the built-in set, pass a `ViewRegistry` to the chat. See the [Catalog reference](/docs/chat/a2ui/catalog) for details.
+
+
+## What's Next
+
+
+
+ API reference for A2uiSurfaceComponent — render a surface outside ChatComponent.
+
+
+ API reference for createA2uiSurfaceStore() and the apply/surfaces/surface interface.
+
+
+ Reference for all 12 built-in components and their props.
+
+
+ How the streaming pipeline detects and routes A2UI content.
+
+
diff --git a/apps/website/content/docs/chat/a2ui/surface-component.mdx b/apps/website/content/docs/chat/a2ui/surface-component.mdx
new file mode 100644
index 000000000..187fecef7
--- /dev/null
+++ b/apps/website/content/docs/chat/a2ui/surface-component.mdx
@@ -0,0 +1,116 @@
+# A2uiSurfaceComponent
+
+Angular component that renders a single A2UI surface. It converts the surface's component map and data model into a json-render `Spec`, then delegates rendering to `RenderSpecComponent`.
+
+**Import:**
+
+```typescript
+import { A2uiSurfaceComponent } from '@cacheplane/chat';
+```
+
+**Selector:** `a2ui-surface`
+
+## Inputs
+
+| Input | Type | Required | Description |
+|-------|------|----------|-------------|
+| `surface` | `A2uiSurface` | Yes | The surface to render |
+| `catalog` | `ViewRegistry` | Yes | Component registry used to resolve A2UI type names to Angular components |
+
+## How It Works
+
+`A2uiSurfaceComponent` bridges the A2UI surface model to the json-render engine in three steps:
+
+**1. Convert surface to Spec (`surfaceToSpec`)**
+
+The `surfaceToSpec()` function walks the flat component map and produces a json-render `Spec`. It requires a component with `id: 'root'` to be present — if no root exists, nothing renders.
+
+**2. Resolve dynamic values**
+
+Before the spec is emitted, each component prop is evaluated against the surface's data model. A prop can be:
+
+- A literal value — passed through as-is
+- A path reference `{ path: '/some/pointer' }` — resolved via JSON Pointer against `dataModel`
+- A function call `{ call: 'formatCurrency', args: { ... } }` — executed by the built-in function registry
+- A template string `"Hello ${/name}"` — interpolated with values from `dataModel`
+
+**3. Expand template children**
+
+When a component's `children` field is an `A2uiChildTemplate` (`{ path, componentId }`), the surface component expands it over the array at `path` in the data model. Each array item gets its own cloned element with props resolved in that item's scope.
+
+**4. Render via RenderSpecComponent**
+
+The final `Spec` is passed to `RenderSpecComponent` along with the `ViewRegistry` (converted to an Angular registry via `toRenderRegistry`). Rendering is fully reactive — when the surface signal updates, the spec recomputes and only affected elements re-render.
+
+## Usage Outside ChatComponent
+
+`A2uiSurfaceComponent` is used internally by `ChatComponent`, but you can also use it directly to embed a surface in any Angular template.
+
+```typescript
+import { Component, signal } from '@angular/core';
+import { A2uiSurfaceComponent, createA2uiSurfaceStore, a2uiBasicCatalog } from '@cacheplane/chat';
+import { createA2uiMessageParser } from '@cacheplane/a2ui';
+
+@Component({
+ selector: 'app-agent-panel',
+ standalone: true,
+ imports: [A2uiSurfaceComponent],
+ template: `
+ @if (store.surface('dashboard')() as surface) {
+
+ }
+ `,
+})
+export class AgentPanelComponent {
+ readonly catalog = a2uiBasicCatalog();
+ readonly store = createA2uiSurfaceStore();
+ private readonly parser = createA2uiMessageParser();
+
+ receiveChunk(chunk: string): void {
+ for (const msg of this.parser.push(chunk)) {
+ this.store.apply(msg);
+ }
+ }
+}
+```
+
+
+The surface must contain a component with `id: 'root'`. If no root component has been received yet (for example, while the agent is still streaming), `A2uiSurfaceComponent` renders nothing until one arrives.
+
+
+## surfaceToSpec()
+
+The conversion function is exported for testing or custom rendering pipelines.
+
+**Import:**
+
+```typescript
+import { surfaceToSpec } from '@cacheplane/chat';
+```
+
+**Signature:**
+
+```typescript
+function surfaceToSpec(surface: A2uiSurface): Spec | null
+```
+
+Returns `null` when the surface has no `root` component. Otherwise returns a complete json-render `Spec` with all dynamic values resolved against the current `dataModel`.
+
+## What's Next
+
+
+
+ Manage surfaces and apply A2UI messages reactively.
+
+
+ All 12 built-in components and their props.
+
+
diff --git a/apps/website/content/docs/chat/a2ui/surface-store.mdx b/apps/website/content/docs/chat/a2ui/surface-store.mdx
new file mode 100644
index 000000000..3d0174a61
--- /dev/null
+++ b/apps/website/content/docs/chat/a2ui/surface-store.mdx
@@ -0,0 +1,148 @@
+# createA2uiSurfaceStore()
+
+Factory function that creates an `A2uiSurfaceStore` — a reactive store that accumulates A2UI messages into a live `Map` of surfaces.
+
+**Import:**
+
+```typescript
+import { createA2uiSurfaceStore } from '@cacheplane/chat';
+```
+
+## Signature
+
+```typescript
+function createA2uiSurfaceStore(): A2uiSurfaceStore
+```
+
+**Returns:** `A2uiSurfaceStore` — a stateful store backed by Angular signals. Can be created outside an injection context.
+
+## A2uiSurfaceStore Interface
+
+```typescript
+interface A2uiSurfaceStore {
+ /** Apply an A2UI message, updating surfaces reactively. */
+ apply(message: A2uiMessage): void;
+
+ /** Signal containing all current surfaces, keyed by surfaceId. */
+ readonly surfaces: Signal