Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/a2ui-project/a2ui/llms.txt

Use this file to discover all available pages before exploring further.

A2UI cleanly separates UI structure (the component tree) from application state (the data model). Component definitions describe what the interface looks like; the data model holds the values those components display. Binding a component property to a JSON Pointer path means the client updates that property automatically whenever the underlying data changes — the agent never has to resend component definitions just to change a displayed value.

The Two Layers

LayerSent viaChanges via
UI StructureupdateComponentsSend a new updateComponents for changed components
Application StateupdateDataModelSend a targeted updateDataModel to any path
This separation enables three powerful patterns:
  • Reactive updates — change a data path; bound components re-render automatically.
  • Dynamic lists — bind a container’s children to an array path; the list grows and shrinks with the data.
  • Bidirectional sync — user input on form components flows back into the data model without a round-trip to the agent.

The Data Model

Each surface owns a single JSON object as its state store. There is no global store — the model is scoped to the surface’s surfaceId. A typical model might look like:
{
  "user": {
    "name": "Alice",
    "email": "alice@example.com"
  },
  "cart": {
    "items": [
      { "name": "Widget", "price": "$9.99", "quantity": 2 }
    ],
    "total": "$19.98"
  },
  "ui": {
    "loading": false
  }
}

JSON Pointer Paths

A2UI uses RFC 6901 JSON Pointer syntax to address values inside the data model:
PathResolves to
/user/name"Alice"
/cart/items/0/price"$9.99"
/cart/total"$19.98"
/ui/loadingfalse
Paths always start with /. Array elements use zero-based numeric segments (/cart/items/0).

Binding Syntax

To bind a component property to a data model path, pass a { "path": "..." } object instead of a literal value.
Literal value (static):
{
  "id": "page-title",
  "component": "Text",
  "text": "My Dashboard"
}
Data-bound value (reactive):
{
  "id": "user-name",
  "component": "Text",
  "text": { "path": "/user/name" }
}
When /user/name changes from "Alice" to "Bob", the user-name component automatically re-renders to display "Bob" — no updateComponents needed.

Updating the Data Model

Use updateDataModel to write any JSON value to any path. Affected components re-render immediately.
Initialize the full model:
{
  "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "dashboard",
    "path": "/",
    "value": {
      "user": { "name": "Alice", "email": "alice@example.com" },
      "cart": { "items": [], "total": "$0.00" }
    }
  }
}
Update a single field:
{
  "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "dashboard",
    "path": "/user/name",
    "value": "Bob"
  }
}
Replace an array:
{
  "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "dashboard",
    "path": "/cart/items",
    "value": [
      { "name": "Widget", "price": "$9.99", "quantity": 3 }
    ]
  }
}
Delete a key (omit value):
{
  "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "dashboard",
    "path": "/ui/loading"
  }
}

End-to-End Reactive Example

This example shows a complete binding cycle: a component bound to a path, the initial data that populates it, and a subsequent update that causes an automatic re-render. 1. Declare the component with a binding:
{
  "version": "v0.9.1",
  "updateComponents": {
    "surfaceId": "order-tracker",
    "components": [
      {
        "id": "status-display",
        "component": "Text",
        "text": { "path": "/order/status" },
        "variant": "body"
      }
    ]
  }
}
2. Seed the data model:
{
  "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "order-tracker",
    "path": "/order/status",
    "value": "Processing…"
  }
}
The status-display component now shows "Processing…". 3. Update the data later — no component redefinition needed:
{
  "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "order-tracker",
    "path": "/order/status",
    "value": "Shipped ✓"
  }
}
The status-display component automatically re-renders to show "Shipped ✓".

Dynamic Lists

Bind a container’s children to an array path to render one template component instance per array item. As the array grows or shrinks, the rendered list updates automatically.
{
  "id": "product-list",
  "component": "Column",
  "children": {
    "path": "/products",
    "componentId": "product-card"
  }
}
Inside the product-card template, paths are scoped to the current array item. A path like { "path": "/name" } resolves to /products/0/name for the first item, /products/1/name for the second, and so on:
{
  "id": "product-card",
  "component": "Card",
  "child": "product-info"
}
{
  "id": "product-info",
  "component": "Column",
  "children": ["product-name", "product-price"]
}
{
  "id": "product-name",
  "component": "Text",
  "text": { "path": "/name" }
}
{
  "id": "product-price",
  "component": "Text",
  "text": { "path": "/price" },
  "variant": "caption"
}
Seed the array:
{
  "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "store",
    "path": "/products",
    "value": [
      { "name": "Widget", "price": "$9.99" },
      { "name": "Gadget", "price": "$19.99" }
    ]
  }
}
This produces two rendered cards. Adding a third item to /products automatically renders a third card.

Bidirectional Input Binding

Input components write user changes back into the data model automatically. The agent does not need to listen for individual keystrokes — it reads the full data model value when the user submits.
ComponentBinding propertyUser actionData model update
TextFieldvalueTypes "Alice"/form/name"Alice"
CheckBoxvalueChecks the box/form/agreedtrue
ChoicePickervalueSelects "ca"/form/country["ca"]
SlidervalueDrags to 72/settings/volume72
DateTimeInputvaluePicks a date/booking/date → ISO string

Best Practices

Use granular path updates. Updating /user/name is far more efficient than replacing the entire root / object — only the affected components re-render:
{
  "version": "v0.9.1",
  "updateDataModel": {
    "surfaceId": "profile",
    "path": "/user/name",
    "value": "Alice"
  }
}
Organize the model by domain. Group related state under a common prefix to make updates predictable:
{
  "user":  { "name": "Alice",  "email": "..." },
  "cart":  { "items": [...],   "total": "..." },
  "ui":    { "loading": false, "error": null  }
}
Pre-format display values on the agent. A2UI does not support client-side expression evaluation for formatting. Compute display strings before sending:
{ "price": "$19.99" }
Not:
{ "price": 19.99 }
Client-side expressions (v0.9+) are part of the specification but are catalog- and renderer-dependent. Check your catalog definition for supported expression syntax before relying on them.

Message Reference

Full schema for updateDataModel and all other A2UI messages.

Component Reference

Which properties on each component support data binding.

Catalogs

Define your own catalog with custom component schemas.

Transports

Deliver data model updates over A2A, AG-UI, and more.

Build docs developers (and LLMs) love