Let the agent choose the screen, not invent it.
The moment an agent renders a form instead of describing one, your test matrix stops being finite — every component you own can now appear in a composition nobody wrote a story for, and the first production symptom is a blank region where a card should be. Generative UI is worth that cost in exactly one situation: when you cannot write the list of screens down. Everywhere else, build the screens and let the agent pick among them.
Know what you are buying, and what it costs.
The problem generative UI solves is real and specific. An agent that can only emit prose forces every structured interaction back through the chat box: the user reads a paragraph describing four flight options and then types "the second one, but Tuesday". That round trip is where task completion goes to die — it is slow, it is ambiguous, and it is the single most common reason a capable agent produces an abandoned session.
What you pay for fixing it is the end of a finite test matrix. A conventional interface has screens; someone designed each one, a reviewer looked at it, and it changes when a person deploys. An agent-composed interface has a space of screens, and you can only characterise it statistically. Three things you previously did once, per screen, now have to hold for every composition: the rendering is correct, the reading order is sane, and the strings are in the user's language.
The test: can you write the list of screens on a whiteboard? If yes, you do not need generative UI for them — build them, and give the agent a tool that selects one and fills it in. You keep your tests, your accessibility review, and your ability to say what a user saw. Generative UI earns its cost only where the list is genuinely open-ended.
Pick the lowest rung of authorship that does the job.
There are four levels of agent authorship, and they are not a maturity ladder — they are a menu where the cheapest adequate option wins. Most products need two of them at once, on different surfaces.
- Prose. The agent writes text. No structure to get wrong, no rendering to review, and the user retypes anything structured. Still the right answer for most turns.
- Selection. The agent calls a tool that names one of your screens and supplies its props. Fully enumerable, fully testable, and the option teams skip because it feels less impressive. It solves the flight-options problem completely.
- Composition. The agent assembles a screen from a catalog of components you own — the shape standardised by A2UI, where a JSON component list is mapped onto your native widgets. Open-ended, and the subject of the rest of this page.
- Installation. A third party ships a coded interface that your host renders in a sandbox — the MCP Apps shape. Not the agent authoring anything; a dependency with a UI attached, and governed like any other dependency.
The design error worth naming is jumping from prose straight to composition because the demo is better. Selection covers a startling share of real cases at a fraction of the risk, and it is the rung you should exhaust first. See progressive disclosure for how much structure a turn actually needs.
The catalog is a contract with an untrusted caller.
Once a model composes your components, each one is a public API whose caller does not read documentation, cannot be trained, and will pass combinations your designer never considered. Most design systems fail this bar, because until now the only caller was a developer who would notice a red squiggle.
- Every component total over its prop space. Missing value, empty array, absurdly long string, wrong type — each has a defined rendering, and none of them is a crash or an empty box. This is the single highest-return item on this page.
- Validate at the boundary, not in the component. Parse the incoming payload against a schema before anything mounts, and reject rather than half-render. A component defending itself individually produces a screen that is half right, which is worse than one that visibly failed.
- Define the unknown-component behavior. The model will eventually request a component type your client does not implement. Silently dropping it is how you get the blank region — the characteristic production failure of this pattern. Render a labelled placeholder in development and a clean fallback in production, and count the occurrences.
- Keep the catalog small on purpose. Every component you add multiplies the composition space and the prompt tokens describing it. A dozen well-chosen primitives beat forty specialised ones, and the model composes the small catalog more reliably.
- Never let the model supply raw markup, styles, or URLs. Props are data. The moment a prop is interpreted as markup you have re-invented the problem both UI standards exist to avoid — see rendering agent output safely.
The generated surface is never the system of record.
The most dangerous thing about a rendered form is how convincing it looks. A user sees a labelled field, an amount, and a Confirm button, and reasonably assumes the same machinery is behind it as behind the rest of your product. Make that assumption true by routing everything through the paths that already exist.
- Submissions go through the same validated tool call a human-built screen would use, with the same authorization checks and the same server-side validation. The surface validates nothing on its own and must never be the only place a rule is enforced.
- The agent supplies data, not authority. If the model chose the amount in the field, the write path still checks that this user may move that amount. A composed screen is an input to your system, in the same trust class as a request body.
- Confirmations must restate the effect from the server's view. A Confirm button whose label the model wrote is not consent to the operation the tool will actually perform. Show the resolved action — see approval & confirmation UX.
- Anything irreversible keeps its normal guardrails. Being inside a generated surface is not a reason to skip the undo path; if anything, a screen the user has never seen before needs it more.
Stated as one rule: a generated surface may display anything and may decide nothing. Every decision it appears to make is really made by a tool behind it, under the rules that tool already had.
The failures that only show up in production.
Five, in the order teams actually hit them:
- The untested composition. A field rendered with no form around it; a card whose value is missing; two components stacked in an order that reads as a single sentence and says something false. Nothing threw. Only STEP 3 prevents this.
- Accessibility falls through the gap. Each component may be perfectly accessible and the composition still be unusable — reading order, focus management and landmark structure are properties of the assembly, and no one reviewed the assembly. If you take one action here, make it a keyboard-only pass over a dozen sampled real compositions.
- Half-translated screens. Component chrome comes from your catalog and translates; every string the model wrote does not, unless the model was told the locale and reminded in the same breath. Mixed-language screens are the most visible defect in this pattern and the easiest to prevent.
- The latency cliff. A composed screen cannot render until enough of the payload has arrived. A2UI's flat component list with ID references exists precisely so a client can render progressively — use that, and never let a spinner replace text the user could already have been reading. See waiting & latency UX.
- Silent drift. A prompt tweak or a model upgrade changes what gets composed, with no deploy on your side. Your release process does not gate this surface, which makes it a monitoring problem rather than a design one.
Test it like a distribution, and keep an exit to text.
You cannot enumerate the outputs, so stop trying to and test the way you would test any generative behavior. Three mechanisms cover most of it:
- Snapshot the payload, not the pixels. Log the component list and data model — or the template ID and version — on every turn that renders. It is small, it is diffable, and it is the only artefact that will tell you what a user was looking at when they report that the screen was wrong. Screenshots cannot be reconstructed after the fact; payloads can be replayed.
- Replay a golden set through the renderer in CI. A few hundred captured payloads, rendered headlessly, asserting the cheap invariants: nothing empty, no unknown component types, no missing required props, no untranslated string in a non-English locale. This catches catalog regressions, which are the ones that hit every user at once.
- Fuzz the catalog directly. Generate compositions from the schema rather than from the model — random types, missing values, deep nesting — and assert nothing crashes. Cheaper than model-driven testing and it finds the totality bugs from STEP 3 faster.
Then measure the thing that justifies the feature: task completion without falling back to the chat box. Not engagement with the rendered surface, which rises simply because the surface is new. If users are still retyping structured answers into the transcript, the composition is not carrying the interaction and you have taken on an open-ended test surface for nothing.
Ship selection before composition — one tool that names a screen you built and fills it in solves most of the real problem in a week, with your test suite intact. If you do adopt composition, spend the first sprint on totality: every component defined over missing, empty and oversized values, plus an explicit render for unknown component types. Log the payload on every rendered turn from day one; it costs nothing and it is unrecoverable later. And keep a flag that drops the whole surface back to prose, because the day a model upgrade changes what gets composed, that flag is your only fast control.
Related: designing for failure for the degraded path this needs, designing for trust for why a convincing surface raises the bar rather than lowering it, and agent interoperability for where A2UI and MCP Apps sit in the protocol stack.