@ant-design/x-card is a dynamic card rendering component based on the A2UI protocol, enabling AI Agents to dynamically build and render interactive UIs through structured JSON message streams.
A2UI (Agent-to-User Interface) is an open protocol that allows AI Agents to describe interaction intent through declarative JSON message sequences, which the frontend runtime dynamically renders into native UI components.
A2UI is built on three core ideas:
Unlike traditional approaches where AI generates HTML directly, A2UI uses structured data streams with significant advantages:
| Feature | A2UI | AI-generated HTML |
|---|---|---|
| Security | Only uses predefined component catalog, no code execution risk | May contain malicious scripts, injection risk |
| Cross-platform | One data structure auto-adapts to Web, mobile, and other native components | HTML requires extra adaptation per platform |
| Streaming Rendering | Supports progressive rendering for smooth UX | Requires complete response before rendering |
| LLM-friendly | Flat JSON structure supports incremental generation, reduces AI burden | Requires generating full HTML structure, prone to syntax errors |
| Maintenance | Components managed centrally, updates only require client library changes | Each HTML interface needs individual debugging |
A2UI follows the unidirectional data flow principle, ensuring predictable data direction:
Agent (LLM) → A2UI Generator → Transport (SSE/WebSocket/A2A)↓Client (Stream Reader) → Message Parser → Renderer → Native UI
Using a restaurant booking as an example:
sequenceDiagramparticipant Userparticipant Clientparticipant AgentUser->>Client: "Book a table for 2 tomorrow at 7pm"Client->>Agent: Send user requestNote over Agent: 1. Create UI containerAgent->>Client: createSurface { surfaceId: "booking" }Note over Agent: 2. Define UI structureAgent->>Client: updateComponents { components: [...] }Note over Agent: 3. Populate initial dataAgent->>Client: updateDataModel { datetime: "2025-12-16T19:00", guests: 2 }Note over Client: 4. Render booking formClient->>User: Show date picker and guest count inputUser->>Client: Change guest count to 3Note over Client: Auto-updates /reservation/guestsUser->>Client: Click "Confirm" buttonClient->>Agent: userAction { name: "confirm", context: { guests: 3 } }Note over Agent: 5. Handle user actionAgent->>Client: deleteSurface { surfaceId: "booking" }Agent->>Client: Create success confirmation UI
@ant-design/x-card supports both v0.8 and v0.9 of the A2UI protocol. Understanding the differences helps you choose the right version and migrate if needed.
| Feature | v0.8 | v0.9 |
|---|---|---|
| Version field | No explicit version field | Explicit version: 'v0.9' field |
| Surface creation | Implicit (auto-created on first updateComponents) | Explicit createSurface command |
| Data model update | Uses contents array | Uses path and value fields |
| Component definition | More complex nested structure | Simpler flat structure |
| Recommendation | Deprecated, compatibility only | Recommended |
v0.8 uses implicit Surface creation — a Surface is automatically created when the Agent sends the first updateComponents:
// v0.8 has no explicit version field{updateComponents: {surfaceId: 'booking',catalogId: 'https://example.com/catalogs/booking/v1/catalog.json',components: [{id: 'root',component: 'Column',children: ['header', 'content']}]}}
Data model updates use the contents array:
{updateDataModel: {surfaceId: 'booking',contents: [{op: 'replace',path: '/reservation/guests',value: 3}]}}
v0.9 introduces explicit version identification and a Surface creation command, making the protocol clearer and more controllable:
// Explicitly create Surface{version: 'v0.9',createSurface: {surfaceId: 'booking',catalogId: 'https://example.com/catalogs/booking/v1/catalog.json'}}// Update components{version: 'v0.9',updateComponents: {surfaceId: 'booking',components: [{id: 'root',component: 'Column',children: ['header', 'content']}]}}
Data model updates use the more intuitive path and value fields:
{version: 'v0.9',updateDataModel: {surfaceId: 'booking',path: '/reservation/guests',value: 3}}
If you are using v0.8, follow these steps to migrate to v0.9:
Add version: 'v0.9' to all messages:
// v0.8{ updateComponents: { ... } }// v0.9{ version: 'v0.9', updateComponents: { ... } }
Send createSurface before updateComponents:
// v0.8: implicit creation{ updateComponents: { surfaceId: 'booking', catalogId: '...', components: [...] } }// v0.9: explicit creation[{ version: 'v0.9', createSurface: { surfaceId: 'booking', catalogId: '...' } },{ version: 'v0.9', updateComponents: { surfaceId: 'booking', components: [...] } }]
Replace the contents array with path + value:
// v0.8{updateDataModel: {surfaceId: 'booking',contents: [{ op: 'replace', path: '/guests', value: 3 }]}}// v0.9{version: 'v0.9',updateDataModel: {surfaceId: 'booking',path: '/guests',value: 3}}
v0.9 supports updating entire objects, reducing message count:
// v0.8: multiple messages required[{ updateDataModel: { surfaceId: 'booking', contents: [{ op: 'add', path: '/date', value: '2025-12-16' }] } },{ updateDataModel: { surfaceId: 'booking', contents: [{ op: 'add', path: '/guests', value: 2 }] } }]// v0.9: one message is enough{version: 'v0.9',updateDataModel: {surfaceId: 'booking',path: '/reservation',value: { date: '2025-12-16', guests: 2 }}}
@ant-design/x-card supports both versions simultaneously:
import type { XAgentCommand_v0_8, XAgentCommand_v0_9 } from '@ant-design/x-card';// Auto-detect version and handle correctlyconst commands: (XAgentCommand_v0_8 | XAgentCommand_v0_9)[] = [// v0.8 message{updateComponents: {/* ... */},},// v0.9 message{version: 'v0.9',createSurface: {/* ... */},},];<XCard.Box commands={commands}>{/* ... */}</XCard.Box>;
The component automatically detects the protocol version based on the presence of the version field and handles messages correctly.
@ant-design/x-card fully implements the A2UI v0.9 core command system:
Creates a new UI container (Surface). Each Surface has its own independent component tree and data model.
{version: 'v0.9',createSurface: {surfaceId: 'booking', // Unique surface identifiercatalogId: 'https://example.com/catalogs/booking/v1/catalog.json' // Component catalog}}
Defines or updates UI components in a Surface using the adjacency list model.
{version: 'v0.9',updateComponents: {surfaceId: 'booking',components: [{ id: 'root', component: 'Column', children: ['header', 'guests-field', 'submit-btn'] },{ id: 'header', component: 'Text', text: 'Confirm Reservation', variant: 'h1' },{ id: 'guests-field', component: 'TextField', label: 'Guests', value: { path: '/reservation/guests' } },{id: 'submit-btn',component: 'Button',variant: 'primary',child: 'submit-text',action: {event: { name: 'confirm', context: { details: { path: '/reservation' } } }}}]}}
Updates the Surface's application state, triggering reactive UI updates.
{version: 'v0.9',updateDataModel: {surfaceId: 'booking',path: '/reservation',value: {datetime: '2025-12-16T19:00:00Z',guests: 2}}}
Removes the specified Surface and all its components and data model.
{version: 'v0.9',deleteSurface: {surfaceId: 'booking'}}
A2UI separates UI structure from application state, enabling reactive updates through data binding.
Each Surface has an independent JSON data model:
{"user": { "name": "Alice", "email": "alice@example.com" },"reservation": { "datetime": "2025-12-16T19:00:00Z", "guests": 2 }}
Uses RFC 6901 standard JSON Pointer to access
/user/name → "Alice"/reservation/guests → 2Component properties can use literal values or data binding:
// Literal (static){ id: 'title', component: 'Text', text: 'Welcome' }// Path binding (dynamic){ id: 'username', component: 'Text', text: { path: '/user/name' } }
When /user/name changes from "Alice" to "Bob", the text updates automatically.
Interactive components can automatically update the data model:
{ id: 'name-input', component: 'TextField', value: { path: '/form/name' } }
User input automatically updates /form/name.
User interactions are passed back to the Agent via action events.
// Component definition{id: 'submit-btn',component: 'Button',action: {event: {name: 'confirm_booking',context: {date: { path: '/reservation/datetime' },guests: { path: '/reservation/guests' }}}}}
When the user clicks the button, the client sends:
{version: 'v0.9',action: {name: 'confirm_booking',surfaceId: 'booking',sourceComponentId: 'submit-btn',timestamp: '2025-12-16T19:05:00Z',context: { date: '2025-12-16T19:00:00Z', guests: 3 }}}
The Catalog defines available components and their property schemas, ensuring type safety and validation.
registerCatalog(catalog);<XCard.Boxcomponents={{Text: MyTextComponent,Button: MyButtonComponent,TextField: MyTextFieldComponent,}}>{/* ... */}</XCard.Box>;
Users see the UI build up incrementally without waiting for the full response.
Uses a flat component list instead of a nested tree structure — LLM-friendly, supports incremental updates, and fault-tolerant.
Automatically validates component properties against the Catalog. Friendly errors in development, graceful degradation in production.
Full TypeScript type definitions:
import type {XAgentCommand_v0_9,XAgentCommand_v0_8,ActionPayload,Catalog,CatalogComponent,} from '@ant-design/x-card';
npm install @ant-design/x-card# oryarn add @ant-design/x-card# orpnpm add @ant-design/x-card
import { XCard, registerCatalog } from '@ant-design/x-card';import type { XAgentCommand_v0_9, Catalog, ActionPayload } from '@ant-design/x-card';const catalog: Catalog = {catalogId: 'my-app-catalog',components: {Text: {/* ... */},Button: {/* ... */},},};registerCatalog(catalog);const commands: XAgentCommand_v0_9[] = [{ version: 'v0.9', createSurface: { surfaceId: 'booking', catalogId: 'my-app-catalog' } },{version: 'v0.9',updateComponents: {surfaceId: 'booking',components: [/* ... */],},},];function App() {const handleAction = (payload: ActionPayload) => {console.log('Action triggered:', payload.name, payload.context);};return (<XCard.Boxcommands={commands}onAction={handleAction}components={{ Text: MyTextComponent, Button: MyButtonComponent }}><XCard.Card id="booking" /></XCard.Box>);}