A2UI · Reference

A2UI Schema

The @threadplane/a2ui schema is a TypeScript model of the protocol shapes the framework uses. It's a contract for agent output and custom integrations, but it's not a runtime validator.

#Dynamic values

Dynamic values are wrapped objects. A value can be literal or resolved from the surface data model by path.

type DynamicString =
  | { literalString: string }
  | { path: string };
 
type DynamicNumber =
  | { literalNumber: number }
  | { path: string };
 
type DynamicBoolean =
  | { literalBoolean: boolean }
  | { path: string };
 
type DynamicStringList =
  | { literalArray: string[] }
  | { path: string };

Absolute paths start with / and are resolved from the model root. Relative paths are resolved from an optional A2uiScope.

#Children

Layout components use either explicit child IDs or a template declaration.

type A2uiChildren =
  | { explicitList: string[] }
  | { template: { componentId: string; dataBinding: string } };

The protocol layer only types this shape. Template expansion is renderer behavior.

#Actions

An action has a name and optional context entries. Context values use the same dynamic wrappers as component props.

interface A2uiAction {
  name: string;
  context?: A2uiActionContextEntry[];
}

@threadplane/a2ui does not execute actions. It only describes the payload that chat/render code can turn into an outbound A2uiActionMessage.

#Components

Every component has an id, optional weight, and a single-key component union.

interface A2uiComponent {
  id: string;
  weight?: number;
  component: A2uiComponentDef;
}

The exported component definitions are:

DefinitionMain fields
Texttext, usageHint
Imageurl, alt, width, height
Iconicon, size
Videourl, autoPlay, controls
AudioPlayerurl, autoPlay, controls
Rowchildren, gap, alignment, distribution
Columnchildren, gap, alignment
Listchildren, direction
Cardchild
TabstabItems
Dividerdirection
ModalentryPointChild, contentChild, title
Buttonchild, primary, action
CheckBoxlabel, checked, action
TextFieldlabel, text, textFieldType, validationRegexp
DateTimeInputlabel, value, enableDate, enableTime
MultipleChoiceselections, options, maxAllowedSelections, label
Slidervalue, minValue, maxValue, step, label

Several of these fields are constrained to a fixed enum. Emit one of the listed values — an unknown value isn't validated at the protocol layer, but a renderer may ignore it or fall back to a default:

FieldOnAllowed values
usageHintText'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'caption' | 'body'
textFieldTypeTextField'date' | 'longText' | 'number' | 'shortText' | 'obscured'
alignmentRow, Column'start' | 'center' | 'end' | 'stretch'
distributionRow'start' | 'center' | 'end' | 'space-between' | 'space-around'
directionList'vertical' | 'horizontal'
directionDivider'horizontal' | 'vertical'

Tabs is the one container that doesn't use A2uiChildren. Its tabItems is an array of A2uiTabItem, each pairing a title (a DynamicString) with a single child id:

interface A2uiTabItem {
  title: DynamicString;
  child: string;
}

The schema exposes validationRegexp on TextField, but validation execution is not implemented in this package. Treat schema fields as protocol data until a renderer wires behavior.

#Message envelopes

The parser recognizes four top-level envelopes:

type A2uiMessage =
  | { surfaceUpdate: A2uiSurfaceUpdate }
  | { dataModelUpdate: A2uiDataModelUpdate }
  | { beginRendering: A2uiBeginRendering }
  | { deleteSurface: A2uiDeleteSurface };

#surfaceUpdate

Adds or replaces components for a surface.

{
  "surfaceUpdate": {
    "surfaceId": "checkout",
    "components": [
      { "id": "root", "component": { "Card": { "child": "title" } } },
      { "id": "title", "component": { "Text": { "text": { "literalString": "Checkout" } } } }
    ]
  }
}

#dataModelUpdate

Carries nested data-model entries. path is optional.

{
  "dataModelUpdate": {
    "surfaceId": "checkout",
    "path": "/customer",
    "contents": [
      { "key": "name", "valueString": "Ada" },
      { "key": "active", "valueBoolean": true }
    ]
  }
}

Each A2uiDataModelEntry has a key plus one of valueString, valueNumber, valueBoolean, or valueMap.

#beginRendering

Identifies the root component to render for a surface.

{
  "beginRendering": {
    "surfaceId": "checkout",
    "root": "root",
    "styles": {
      "font": "Inter",
      "primaryColor": "#2563eb"
    }
  }
}

The source comments describe styles.font and styles.primaryColor as the canonical style fields.

#deleteSurface

Removes a surface by ID.

{ "deleteSurface": { "surfaceId": "checkout" } }

#Internal surface model

A2uiSurface is an internal model used after messages are applied:

interface A2uiSurface {
  surfaceId: string;
  catalogId: string;
  theme?: A2uiTheme;
  sendDataModel?: boolean;
  components: Map<string, A2uiComponent>;
  dataModel: Record<string, unknown>;
  styles?: { font?: string; primaryColor?: string };
}

A2uiTheme carries optional, agent-supplied presentation hints:

interface A2uiTheme {
  primaryColor?: string;
  iconUrl?: string;
  agentDisplayName?: string;
}

This shape is not constrained to the wire format. Do not assume an agent sends it directly.

#Outbound action messages

When a rendered surface sends an action back to the agent, the typed outbound shape is:

interface A2uiActionMessage {
  version: 'v1';
  action: {
    name: string;
    surfaceId: string;
    sourceComponentId: string;
    timestamp: string;
    context: Record<string, unknown>;
    label?: string;
  };
  metadata?: {
    a2uiClientDataModel: A2uiClientDataModel;
  };
}

The outbound action version is v1. The optional label is a human-readable label derived from the source component's text (for example, a Button's child Text), used to describe the action in transcripts; backends may ignore it.