A2UI Renderer Production-Readiness Guide
A renderer turns agent-produced data into interactive controls, so it sits on a security boundary. The agent may be trusted to answer a question while its generated payload still contains malformed values, unsupported component types, unsafe URLs, or actions the user did not intend to authorize. Production readiness therefore means more than making a demo render: the host must remain in control when input is incomplete, delayed, duplicated, hostile, or based on a different protocol version.
This guide targets teams preparing a web renderer or host integration. A2UI v0.9.1 is the current production line in the official repository, while v1.0 is a candidate specification. Pin one version throughout the agent, transport, schema validator, catalog, and renderer. Never merge message names or component shapes from different versions in one stream.
1. Draw the trust boundary
Document which system produces each value and which system is allowed to act on it. Treat the following as untrusted until the host validates them:
- every message received from an agent or model;
- catalog identifiers and component names;
- text, URLs, media sources, styles, and data bindings;
- action names, parameters, and destination identifiers;
- incremental updates that refer to an existing surface.
The renderer should only instantiate components registered in a host-owned catalog. A payload must not select arbitrary modules, execute embedded code, inject raw HTML, or turn a string into a privileged application command. The catalog is an allowlist and a compatibility contract, not a discovery mechanism for remote code.
2. Validate before changing state
Validate each message against the schema for the pinned protocol version before it reaches rendering state. Reject unknown top-level messages, unknown properties where the schema forbids them, invalid identifiers, unsupported catalogs, incorrect value types, and oversized input.
Validation also needs semantic checks. Confirm that a referenced surfaceId exists, component IDs are unique where required, bindings point to permitted data paths, and component relationships do not create impossible or excessively deep trees. Parse JSON with a real parser; do not repair malformed model output silently because a repair can change the intended action.
Return a structured, non-sensitive error to the agent or transport layer. The user-facing interface should preserve the last valid surface or display a stable fallback instead of going blank.
3. Enforce lifecycle rules
For v0.9, the lifecycle uses createSurface, updateComponents, updateDataModel, and deleteSurface. Keep lifecycle state per surface and make invalid transitions explicit. An update for an unknown surface should not create one implicitly. A duplicate creation should not overwrite an active surface. After deletion, remove component state, data bindings, pending callbacks, and subscriptions associated with that surface.
Streaming introduces ordering problems. Assign transport sequence information when the surrounding protocol supports it, process messages deterministically, and define how reconnects handle duplicates. Test an update arriving before creation, a deletion during an update, repeated messages, a truncated stream, and a reconnect after partial rendering.
4. Keep catalogs narrow
Start with the smallest component catalog that supports the product. Each component adapter should define accepted properties, defaults, size limits, allowed child relationships, and accessible behavior. Reject components outside that catalog even if another renderer supports them.
URLs deserve their own policy. Allow only required schemes, block script URLs, and consider an allowlist for image or navigation origins. Do not forward agent-provided headers, credentials, or tokens to a remote URL. If rich text is supported, sanitize it using a proven host-side policy and keep raw HTML disabled unless the product has a specific, reviewed need.
5. Authorize actions in the host
An A2UI action is a request, not permission. Resolve action names through a host-owned registry and validate every parameter. Separate harmless local interactions from actions that read private data, change server state, spend money, send messages, upload files, or navigate away.
Sensitive actions should show a clear confirmation that describes the exact effect. Authorization must happen on the server as well as in the client; hiding a button is not access control. Use idempotency protection for operations that could be submitted twice, and make cancellation or retry behavior visible.
Never let the model supply executable JavaScript, SQL, shell commands, arbitrary API routes, or authorization scopes as action payloads. Map a small declarative action name to reviewed application code.
6. Bound resource use
Define limits for message bytes, component count, nesting depth, text length, data-model size, update rate, media dimensions, and active surfaces. Apply timeouts to remote media and action calls. Virtualize large lists instead of rendering every item at once.
Limits should fail predictably. Log the reason with a correlation identifier, show a compact fallback, and allow the surrounding conversation to continue. Test the limits directly; a limit that exists only in documentation does not protect the browser.
7. Preserve accessibility
Generated interfaces must meet the same accessibility bar as hand-authored screens. Use semantic controls, visible labels, logical heading levels, keyboard operation, clear focus indicators, sufficient contrast, and announced validation errors. After streaming updates, do not steal focus. Announce material status changes through an appropriate live region without reading every token.
Component adapters should carry accessibility guarantees so the agent does not need to invent ARIA attributes. Test with keyboard-only navigation, zoom, reduced motion, high contrast, and at least one screen reader. A fallback must also be accessible.
8. Design stable failure states
Create explicit states for loading, empty data, unsupported components, invalid messages, offline transport, action failure, and permission denial. Preserve user-entered values during recoverable updates. Avoid showing raw schema errors, stack traces, prompts, or model output that could expose internal data.
When part of a surface fails, prefer isolating that component over discarding the entire interface. When correctness is uncertain—especially for a consequential action—stop and ask for a new valid response rather than guessing.
9. Observe without collecting excess data
Measure validation failures, unsupported component requests, render duration, action success, retries, surface lifetime, and fallback frequency. Use version, catalog, component, and error codes as dimensions. Avoid logging free-form prompts, private data-model values, authentication material, or full action payloads.
Give each surface and action a correlation identifier that is safe to log. Redact at ingestion, define retention, and sample high-volume events. Observability should explain a failure without recreating the user’s private session.
10. Run a launch matrix
Before launch, test valid fixtures and deliberately invalid fixtures for the pinned schema. Cover slow streams, reordered messages, duplicates, disconnection, unsupported catalogs, malicious URLs, oversized payloads, deep component graphs, action replay, expired authorization, keyboard navigation, narrow screens, localization, and renderer upgrades.
Use contract tests between the agent and renderer, unit tests for component adapters and action mapping, integration tests for lifecycle behavior, and browser tests for accessibility and recovery. Store representative payloads as fixtures so protocol upgrades produce visible diffs.
Launch gate
A renderer is ready only when all of these statements are true:
- one protocol version is pinned end to end;
- every inbound message is schema- and policy-validated;
- only host-approved catalogs, components, URLs, and actions are accepted;
- consequential actions require server authorization and appropriate confirmation;
- lifecycle, streaming, reconnect, and cleanup behavior are deterministic;
- resource limits and safe fallbacks are tested;
- generated controls remain keyboard- and screen-reader-accessible;
- logs are useful without storing secrets or unnecessary personal data;
- rollback and compatibility procedures have been exercised.
Use the official renderer development guide, message reference, and versioned A2UI repository specifications as the source of truth. This site explains implementation decisions; it does not replace the schema for the version you ship.