🎯 Skill Positioning
This skill covers @ant-design/x-card — the React implementation of the A2UI protocol, enabling AI agents to dynamically render rich interactive UIs through structured JSON command streams.
It covers:
XCard.Box + XCard.Card component usage
- A2UI v0.9 command types:
createSurface, updateComponents, updateDataModel, deleteSurface
- Custom component registration and catalog management
- Data binding via JSON Pointer paths (RFC 6901)
- Action handling — sending user events back to the agent
- Streaming progressive rendering patterns
- v0.8 ↔ v0.9 protocol differences
Scope: v0.9 is the recommended protocol. v0.8 is supported for backward compatibility only — prefer v0.9 for all new work.
Table of Contents
📦 Package Overview
| Package |
Responsibility |
@ant-design/x-card |
React renderer for A2UI protocol — XCard.Box, XCard.Card, catalog APIs |
@ant-design/x |
Chat UI components (Bubble, Sender, etc.) — not covered here |
@ant-design/x-sdk |
Data providers, streaming — not covered here |
npm install @ant-design/x-card
Exports:
import {
XCard,
registerCatalog,
loadCatalog,
validateComponent,
clearCatalogCache,
} from '@ant-design/x-card';
import type {
XAgentCommand_v0_9,
XAgentCommand_v0_8,
ActionPayload,
Catalog,
CatalogComponent,
} from '@ant-design/x-card';
// Subcomponents
XCard.Box; // Container: receives commands, owns catalog maps
XCard.Card; // Renderer: renders a single surface by id
🗂️ Component Architecture
XCard.Box
├── owns: catalogMap, surfaceCatalogMap
├── dispatches: commands → all XCard.Card children
├── aggregates: onAction events from all Cards
└── XCard.Card (id="surface-a")
│ ├── owns: component tree, data model, commandVersion
│ └── resolves: data bindings, triggers actions
└── XCard.Card (id="surface-b")
└── ...
XCard.Box Props
interface BoxProps {
commands?: (XAgentCommand_v0_9 | XAgentCommand_v0_8)[];
/** Component names must start with an uppercase letter (React component convention) */
components?: Record<string, React.ComponentType<any>>;
onAction?: (payload: ActionPayload) => void;
children?: React.ReactNode; // Should contain XCard.Card elements
}
XCard.Card Props
interface CardProps {
id: string; // surfaceId to render
}
ActionPayload
interface ActionPayload {
name: string; // from action.event.name
surfaceId: string; // which surface triggered it
/**
* Context passed by component, with path references automatically resolved.
*
* For action.event.context fields using { path: "xxx" } format:
* - X-Card automatically resolves them to { value: "actual_value" }
* - Other properties (like label) are preserved
*
* Example input config:
* { username: { path: "/form/username", label: "用户名" } }
*
* Example resolved context:
* { username: { value: "张三", label: "用户名" } }
*/
context: Record<string, any>;
}
🚀 Quick Start Decision Guide
| If you need to... |
Read first |
| Set up XCard.Box + XCard.Card |
USAGE.md → Basic Setup |
| Send commands from agent to card |
COMMANDS.md |
| Register a custom component catalog |
CATALOG.md → Local Catalog |
| Bind component props to live data |
DATA_BINDING.md |
| Handle user interactions / form submit |
ACTIONS.md |
| Build a streaming progressive UI |
USAGE.md → Streaming |
| Migrate from v0.8 to v0.9 |
COMMANDS.md → v0.8 vs v0.9 |
| Look up full prop types |
API.md |
🛠 Recommended Workflow
- Define your catalog — register a local catalog or use the A2UI Basic Catalog URL.
- Register custom components — pass them via
XCard.Box components prop.
- Create the React tree — wrap surfaces with
XCard.Box, add XCard.Card per surface.
- Feed commands — push
XAgentCommand_v0_9[] into commands prop (typically from streaming agent response).
- Handle actions — receive
ActionPayload in onAction, update commands in response.
Minimal Working Example
import React, { useState } from 'react';
import { XCard, registerCatalog } from '@ant-design/x-card';
import type { XAgentCommand_v0_9, ActionPayload, Catalog } from '@ant-design/x-card';
// 1. Define and register local catalog
const myCatalog: Catalog = {
catalogId: 'local://my_catalog.json',
components: {
Text: {
type: 'object',
properties: { text: { type: 'string' }, variant: { type: 'string' } },
required: ['text'],
},
Button: {
type: 'object',
properties: { text: { type: 'string' }, action: {} },
required: ['text'],
},
},
};
registerCatalog(myCatalog);
// 2. Custom component implementations
const Text: React.FC<{ text: string; variant?: string }> = ({ text, variant }) => (
<p className={`text-${variant ?? 'body'}`}>{text}</p>
);
const Button: React.FC<{ text: string; onAction?: (ctx: any) => void; action?: any }> = ({
text,
onAction,
action,
}) => <button => onAction?.(action?.event?.context ?? {})}>{text}</button>;
// 3. Build commands (from agent stream)
const commands: XAgentCommand_v0_9[] = [
{
version: 'v0.9',
createSurface: {
surfaceId: 'welcome',
catalogId: 'local://my_catalog.json',
},
},
{
version: 'v0.9',
updateComponents: {
surfaceId: 'welcome',
components: [
{ id: 'root', component: 'Column', children: ['title', 'btn'] },
{ id: 'title', component: 'Text', text: { path: '/user/name' }, variant: 'h1' },
{
id: 'btn',
component: 'Button',
text: 'Start',
action: { event: { name: 'start', context: {} } },
},
],
},
},
{
version: 'v0.9',
updateDataModel: {
surfaceId: 'welcome',
path: '/user/name',
value: 'Alice',
},
},
];
// 4. Render
export default function App() {
const [cmdQueue, setCmdQueue] = useState<XAgentCommand_v0_9[]>(commands);
const handleAction = (payload: ActionPayload) => {
console.log('Action:', payload.name, payload.context);
// Append new commands based on agent response
setCmdQueue((prev) => [...prev /* new commands */]);
};
return (
<XCard.Box commands={cmdQueue} components={{ Text, Button }}
<XCard.Card id="welcome" />
</XCard.Box>
);
}
🚨 Development Rules
- Always include
"version": "v0.9" on every command — omitting it causes protocol rejection.
- One and only one
id: "root" component per surface's component tree — this is the tree root.
- Flat adjacency list only — never nest component objects inside other component objects; always reference children by
id string.
- Separate structure from data —
updateComponents for layout, updateDataModel for content/state.
- Register catalog before mounting — call
registerCatalog() before the component tree renders.
- Pass
components map to XCard.Box, not to XCard.Card — Box distributes to all Cards.
- Never recreate the
components object inline — keep it stable with useMemo or module-level constant to avoid re-renders.
- Input components require
value: { path: "..." } for two-way binding — literal values do not update the data model.
- For streaming: append new commands to the array rather than replacing it — Card processes the diff incrementally.
action.event.context paths are write targets — they point to where user-entered data lives in the data model; do not resolve them as read sources.
- Path references in action context are automatically resolved — when an action is triggered, X-Card converts
{ path: "xxx" } in the action config to { value: "actual_value" } in the onAction payload. This works for both v0.9 (action.event.context = { key: { path } }) and v0.8 (action.context = [{ key, value: { path } }]) formats.
🤝 Skill Collaboration
| Scenario |
Skill combination |
| AI chat with structured card responses |
use-x-chat + x-components + x-card |
| Standalone agent form UI |
x-card only |
| Streaming Markdown + card side-panel |
x-markdown + x-card |
| HTTP streaming from agent into card |
x-request → feed response as commands |
🔗 Reference Resources
- USAGE.md — Setup guide, streaming pattern, multi-surface examples
- COMMANDS.md — All four A2UI v0.9 command types, v0.8 vs v0.9 diff
- DATA_BINDING.md — JSON Pointer paths, dynamic types, two-way binding, template iteration
- ACTIONS.md — Action definitions, ActionPayload, form submission pattern
- CATALOG.md — Local catalog registration, remote URL loading, custom component schema
- API.md — Full TypeScript types for Box, Card, commands, catalog, actions
Official Documentation
1---2name: x-card3description: Use when building AI-driven UIs with @ant-design/x-card — covers XCard.Box, XCard.Card, A2UI v0.9 commands, data binding, catalogs, actions, and streaming patterns.4---56# 🎯 Skill Positioning78**This skill covers `@ant-design/x-card`** — the React implementation of the A2UI protocol, enabling AI agents to dynamically render rich interactive UIs through structured JSON command streams.910It covers:1112- `XCard.Box` + `XCard.Card` component usage13- A2UI v0.9 command types: `createSurface`, `updateComponents`, `updateDataModel`, `deleteSurface`14- Custom component registration and catalog management15- Data binding via JSON Pointer paths (RFC 6901)16- Action handling — sending user events back to the agent17- Streaming progressive rendering patterns18- v0.8 ↔ v0.9 protocol differences1920> **Scope**: v0.9 is the recommended protocol. v0.8 is supported for backward compatibility only — prefer v0.9 for all new work.2122## Table of Contents2324- [📦 Package Overview](#-package-overview)25- [🗂️ Component Architecture](#-component-architecture)26- [🚀 Quick Start Decision Guide](#-quick-start-decision-guide)27- [🛠 Recommended Workflow](#-recommended-workflow)28- [🚨 Development Rules](#-development-rules)29- [🤝 Skill Collaboration](#-skill-collaboration)30- [🔗 Reference Resources](#-reference-resources)3132# 📦 Package Overview3334| Package | Responsibility |35| --- | --- |36| `@ant-design/x-card` | React renderer for A2UI protocol — `XCard.Box`, `XCard.Card`, catalog APIs |37| `@ant-design/x` | Chat UI components (Bubble, Sender, etc.) — not covered here |38| `@ant-design/x-sdk` | Data providers, streaming — not covered here |3940```bash41npm install @ant-design/x-card42```4344**Exports:**4546```typescript47import {48 XCard,49 registerCatalog,50 loadCatalog,51 validateComponent,52 clearCatalogCache,53} from '@ant-design/x-card';54import type {55 XAgentCommand_v0_9,56 XAgentCommand_v0_8,57 ActionPayload,58 Catalog,59 CatalogComponent,60} from '@ant-design/x-card';6162// Subcomponents63XCard.Box; // Container: receives commands, owns catalog maps64XCard.Card; // Renderer: renders a single surface by id65```6667# 🗂️ Component Architecture6869```70XCard.Box71├── owns: catalogMap, surfaceCatalogMap72├── dispatches: commands → all XCard.Card children73├── aggregates: onAction events from all Cards74└── XCard.Card (id="surface-a")75│ ├── owns: component tree, data model, commandVersion76│ └── resolves: data bindings, triggers actions77└── XCard.Card (id="surface-b")78 └── ...79```8081## XCard.Box Props8283```typescript84interface BoxProps {85 commands?: (XAgentCommand_v0_9 | XAgentCommand_v0_8)[];86 /** Component names must start with an uppercase letter (React component convention) */87 components?: Record<string, React.ComponentType<any>>;88 onAction?: (payload: ActionPayload) => void;89 children?: React.ReactNode; // Should contain XCard.Card elements90}91```9293## XCard.Card Props9495```typescript96interface CardProps {97 id: string; // surfaceId to render98}99```100101## ActionPayload102103```typescript104interface ActionPayload {105 name: string; // from action.event.name106 surfaceId: string; // which surface triggered it107 /**108 * Context passed by component, with path references automatically resolved.109 *110 * For action.event.context fields using { path: "xxx" } format:111 * - X-Card automatically resolves them to { value: "actual_value" }112 * - Other properties (like label) are preserved113 *114 * Example input config:115 * { username: { path: "/form/username", label: "用户名" } }116 *117 * Example resolved context:118 * { username: { value: "张三", label: "用户名" } }119 */120 context: Record<string, any>;121}122```123124# 🚀 Quick Start Decision Guide125126| If you need to... | Read first |127| --- | --- |128| Set up XCard.Box + XCard.Card | [USAGE.md → Basic Setup](reference/USAGE.md#basic-setup) |129| Send commands from agent to card | [COMMANDS.md](reference/COMMANDS.md) |130| Register a custom component catalog | [CATALOG.md → Local Catalog](reference/CATALOG.md#local-catalog) |131| Bind component props to live data | [DATA_BINDING.md](reference/DATA_BINDING.md) |132| Handle user interactions / form submit | [ACTIONS.md](reference/ACTIONS.md) |133| Build a streaming progressive UI | [USAGE.md → Streaming](reference/USAGE.md#streaming) |134| Migrate from v0.8 to v0.9 | [COMMANDS.md → v0.8 vs v0.9](reference/COMMANDS.md#v08-vs-v09) |135| Look up full prop types | [API.md](reference/API.md) |136137# 🛠 Recommended Workflow1381391. **Define your catalog** — register a local catalog or use the A2UI Basic Catalog URL.1402. **Register custom components** — pass them via `XCard.Box` `components` prop.1413. **Create the React tree** — wrap surfaces with `XCard.Box`, add `XCard.Card` per surface.1424. **Feed commands** — push `XAgentCommand_v0_9[]` into `commands` prop (typically from streaming agent response).1435. **Handle actions** — receive `ActionPayload` in `onAction`, update commands in response.144145## Minimal Working Example146147```tsx148import React, { useState } from 'react';149import { XCard, registerCatalog } from '@ant-design/x-card';150import type { XAgentCommand_v0_9, ActionPayload, Catalog } from '@ant-design/x-card';151152// 1. Define and register local catalog153const myCatalog: Catalog = {154 catalogId: 'local://my_catalog.json',155 components: {156 Text: {157 type: 'object',158 properties: { text: { type: 'string' }, variant: { type: 'string' } },159 required: ['text'],160 },161 Button: {162 type: 'object',163 properties: { text: { type: 'string' }, action: {} },164 required: ['text'],165 },166 },167};168registerCatalog(myCatalog);169170// 2. Custom component implementations171const Text: React.FC<{ text: string; variant?: string }> = ({ text, variant }) => (172 <p className={`text-${variant ?? 'body'}`}>{text}</p>173);174175const Button: React.FC<{ text: string; onAction?: (ctx: any) => void; action?: any }> = ({176 text,177 onAction,178 action,179}) => <button onClick={() => onAction?.(action?.event?.context ?? {})}>{text}</button>;180181// 3. Build commands (from agent stream)182const commands: XAgentCommand_v0_9[] = [183 {184 version: 'v0.9',185 createSurface: {186 surfaceId: 'welcome',187 catalogId: 'local://my_catalog.json',188 },189 },190 {191 version: 'v0.9',192 updateComponents: {193 surfaceId: 'welcome',194 components: [195 { id: 'root', component: 'Column', children: ['title', 'btn'] },196 { id: 'title', component: 'Text', text: { path: '/user/name' }, variant: 'h1' },197 {198 id: 'btn',199 component: 'Button',200 text: 'Start',201 action: { event: { name: 'start', context: {} } },202 },203 ],204 },205 },206 {207 version: 'v0.9',208 updateDataModel: {209 surfaceId: 'welcome',210 path: '/user/name',211 value: 'Alice',212 },213 },214];215216// 4. Render217export default function App() {218 const [cmdQueue, setCmdQueue] = useState<XAgentCommand_v0_9[]>(commands);219220 const handleAction = (payload: ActionPayload) => {221 console.log('Action:', payload.name, payload.context);222 // Append new commands based on agent response223 setCmdQueue((prev) => [...prev /* new commands */]);224 };225226 return (227 <XCard.Box commands={cmdQueue} components={{ Text, Button }} onAction={handleAction}>228 <XCard.Card id="welcome" />229 </XCard.Box>230 );231}232```233234# 🚨 Development Rules235236- **Always include `"version": "v0.9"`** on every command — omitting it causes protocol rejection.237- **One and only one `id: "root"` component** per surface's component tree — this is the tree root.238- **Flat adjacency list only** — never nest component objects inside other component objects; always reference children by `id` string.239- **Separate structure from data** — `updateComponents` for layout, `updateDataModel` for content/state.240- **Register catalog before mounting** — call `registerCatalog()` before the component tree renders.241- **Pass `components` map to `XCard.Box`**, not to `XCard.Card` — Box distributes to all Cards.242- **Never recreate the `components` object inline** — keep it stable with `useMemo` or module-level constant to avoid re-renders.243- **Input components require `value: { path: "..." }` for two-way binding** — literal values do not update the data model.244- **For streaming**: append new commands to the array rather than replacing it — Card processes the diff incrementally.245- **`action.event.context` paths are write targets** — they point to where user-entered data lives in the data model; do not resolve them as read sources.246- **Path references in action context are automatically resolved** — when an action is triggered, X-Card converts `{ path: "xxx" }` in the action config to `{ value: "actual_value" }` in the onAction payload. This works for both v0.9 (`action.event.context = { key: { path } }`) and v0.8 (`action.context = [{ key, value: { path } }]`) formats.247248# 🤝 Skill Collaboration249250| Scenario | Skill combination |251| -------------------------------------- | ---------------------------------------- |252| AI chat with structured card responses | `use-x-chat` + `x-components` + `x-card` |253| Standalone agent form UI | `x-card` only |254| Streaming Markdown + card side-panel | `x-markdown` + `x-card` |255| HTTP streaming from agent into card | `x-request` → feed response as commands |256257# 🔗 Reference Resources258259- [USAGE.md](reference/USAGE.md) — Setup guide, streaming pattern, multi-surface examples260- [COMMANDS.md](reference/COMMANDS.md) — All four A2UI v0.9 command types, v0.8 vs v0.9 diff261- [DATA_BINDING.md](reference/DATA_BINDING.md) — JSON Pointer paths, dynamic types, two-way binding, template iteration262- [ACTIONS.md](reference/ACTIONS.md) — Action definitions, ActionPayload, form submission pattern263- [CATALOG.md](reference/CATALOG.md) — Local catalog registration, remote URL loading, custom component schema264- [API.md](reference/API.md) — Full TypeScript types for Box, Card, commands, catalog, actions265266## Official Documentation267268- [A2UI What Is It](https://a2ui.org/introduction/what-is-a2ui/)269- [A2UI v0.9 Specification](https://a2ui.org/specification/v0.9-a2ui/)270- [Concepts: Data Binding](https://a2ui.org/concepts/data-binding/)271- [Concepts: Catalogs](https://a2ui.org/concepts/catalogs/)272- [Guide: Agent Development](https://a2ui.org/guides/agent-development/)273- [GitHub: google/A2UI](https://github.com/google/A2UI)