export const metadata = { title: "A2UI Integration" } # A2UI Integration Use `@json-render/core` to support [A2UI](https://a2ui.org) natively.

Concept: This page demonstrates how json-render can support A2UI. The examples are illustrative and may require adaptation for production use.

## Native A2UI Support `@json-render/core` is schema-agnostic. Define a catalog that matches A2UI's format and build a renderer that understands it - no conversion layer needed. ## Example A2UI Message A2UI uses an adjacency list model - a flat list of components with ID references. This makes it easy to patch individual components: ```json { "surfaceUpdate": { "surfaceId": "main", "components": [ { "id": "header", "component": { "Text": { "text": {"literalString": "Book Your Table"}, "usageHint": "h1" } } }, { "id": "date-picker", "component": { "DateTimeInput": { "label": {"literalString": "Select Date"}, "value": {"path": "/reservation/date"}, "enableDate": true } } }, { "id": "submit-btn", "component": { "Button": { "child": "submit-text", "action": {"name": "confirm_booking"} } } }, { "id": "submit-text", "component": { "Text": {"text": {"literalString": "Confirm Reservation"}} } } ] } } ``` ## Define the A2UI Catalog ```typescript import { defineCatalog } from '@json-render/core'; import { schema } from '@json-render/react'; import { z } from 'zod'; // A2UI BoundValue schema const BoundString = z.object({ literalString: z.string().optional(), path: z.string().optional(), }).refine(d => d.literalString || d.path); // A2UI children schema const Children = z.object({ explicitList: z.array(z.string()).optional(), template: z.object({ dataBinding: z.string(), componentId: z.string(), }).optional(), }).refine(d => d.explicitList || d.template); export const a2uiCatalog = defineCatalog(schema, { components: { Text: { description: 'Displays text content', props: z.object({ text: BoundString, usageHint: z.enum(['h1', 'h2', 'h3', 'body', 'caption']).optional(), }), }, Button: { description: 'Interactive button', props: z.object({ child: z.string(), action: z.object({ name: z.string(), context: z.array(z.object({ key: z.string(), value: BoundString, })).optional(), }).optional(), }), }, DateTimeInput: { description: 'Date/time picker', props: z.object({ label: BoundString.optional(), value: BoundString.optional(), enableDate: z.boolean().optional(), enableTime: z.boolean().optional(), }), }, Column: { description: 'Vertical layout', props: z.object({ children: Children, }), }, Row: { description: 'Horizontal layout', props: z.object({ children: Children, }), }, // Add more A2UI standard components... }, }); ``` ## Define the A2UI Schema Define the schema for A2UI message types: ```typescript import { z } from 'zod'; // Component instance in the adjacency list const A2UIComponent = z.object({ id: z.string(), component: z.record(z.record(z.unknown())), }); // Surface update message const SurfaceUpdate = z.object({ surfaceId: z.string().optional(), components: z.array(A2UIComponent), }); // State model update message const StateModelUpdate = z.object({ surfaceId: z.string().optional(), path: z.string().optional(), contents: z.array(z.object({ key: z.string(), valueString: z.string().optional(), valueNumber: z.number().optional(), valueBoolean: z.boolean().optional(), valueMap: z.array(z.unknown()).optional(), })), }); // Begin rendering message const BeginRendering = z.object({ surfaceId: z.string().optional(), root: z.string(), catalogId: z.string().optional(), }); // Complete A2UI message schema export const A2UIMessage = z.object({ surfaceUpdate: SurfaceUpdate.optional(), dataModelUpdate: StateModelUpdate.optional(), beginRendering: BeginRendering.optional(), deleteSurface: z.object({ surfaceId: z.string() }).optional(), }); ``` ## Build an A2UI Renderer Create a renderer that processes the A2UI adjacency list format: ```tsx import { a2uiCatalog } from './catalog'; // Component registry const components = { Text: ({ text, usageHint }) => { const Tag = usageHint?.startsWith('h') ? usageHint : 'p'; return {text}; }, Button: ({ children, action, onAction }) => ( ), DateTimeInput: ({ label, value, onChange }) => ( ), Column: ({ children }) =>
{children}
, Row: ({ children }) =>
{children}
, }; // Render A2UI surface export function renderA2UI( componentMap: Map, dataModel: Record, rootId: string, onAction?: (action: any) => void ) { function resolveBoundValue(bound: any) { if (!bound) return undefined; if (bound.literalString) return bound.literalString; if (bound.path) { const parts = bound.path.replace(/^\//, '').split('/'); let value = dataModel; for (const p of parts) value = value?.[p]; return value; } } function render(id: string): React.ReactNode { const comp = componentMap.get(id); if (!comp) return null; const [type, props] = Object.entries(comp.component)[0]; const Component = components[type]; if (!Component) return null; // Resolve props const resolved: any = {}; for (const [key, val] of Object.entries(props as any)) { if (key === 'child') { resolved.children = render(val as string); } else if (key === 'children' && val?.explicitList) { resolved.children = val.explicitList.map(render); } else if (val && typeof val === 'object' && ('literalString' in val || 'path' in val)) { resolved[key] = resolveBoundValue(val); } else { resolved[key] = val; } } return ; } return render(rootId); } ``` ## Usage ```tsx const [components] = useState(() => new Map()); const [dataModel, setDataModel] = useState({}); const [rootId, setRootId] = useState(null); // Process A2UI messages function handleMessage(msg: any) { if (msg.surfaceUpdate) { for (const comp of msg.surfaceUpdate.components) { components.set(comp.id, comp); } } if (msg.dataModelUpdate) { setDataModel(prev => ({ ...prev, ...msg.dataModelUpdate.contents })); } if (msg.beginRendering) { setRootId(msg.beginRendering.root); } } // Render {rootId && renderA2UI(components, dataModel, rootId, handleAction)} ``` ## Next Learn about [Adaptive Cards integration](/docs/adaptive-cards) for another UI protocol.