GENERATIVE-UI ยท August 26, 2026 ยท 4 min read
json-render vs A2UI: Choosing a Generative UI Contract
A fixed spec is easier to validate; A2UI updates over time and sends actions back. Which contract shape fits your surface.
Threadplane gives you two ways to let an agent build UI โ a json-render spec or an A2UI surface โ and this post is about how to pick.
There's a ladder here: markdown when text is the best UI โ a fixed spec when you can validate the whole thing up front โ a live protocol when the surface keeps changing. The mechanical comparison covers what each layer does; this post answers the question that page ends on: which rung is yours?
What's actually different?
This isn't a renderer shootout. The tradeoff is contract shape.
With json-render, the contract is application-owned. You define the schema, you validate the spec before anything mounts, and your handlers own what every event means.
With A2UI, the surface is agent-owned. The agent creates it, keeps updating it over the life of the conversation, and gets structured actions back when the user interacts.
Let's look at the same order card in both shapes. First as a json-render spec:
And as an A2UI JSONL stream:
(I trimmed this one to the total line โ the full card is just more components in the updateComponents envelope. The point is the shape: structure in one message, data in another.)
One is a document you can validate before you show it; the other is a conversation you subscribe to.
When does the fixed spec win?
Whenever the UI is one answer โ the agent responds once, the UI renders once, and it's done.
Let's walk two scenarios to a verdict.
A structured result card in chat: the agent looks up an order and answers with a summary card. Nothing about that card changes after it lands, so there's no ongoing surface to manage. Verdict: json-render. You get to validate the whole spec before mount, and your handlers โ not the protocol โ decide what a click means.
A dashboard or results panel outside chat: your application already has the data and wants a model (or a config file, honestly) to describe the layout.
Verdict: json-render again, driven directly through <render-spec>.
The fixed contract is the feature here: explicit inputs on your own custom components, a schema you can lint, a spec you can snapshot in a test.
One security note: in both paths, the registry is doing allowlist duty. A component name the model emits that isn't registered falls back instead of executing โ json-render and A2UI share that posture, so it's not a reason to pick either.
When does the protocol win?
Whenever the surface has to live past its first render.
Let's take the itinerary case.
The agent proposes a three-day trip mid-conversation: the component structure arrives first, prices and times fill in as updateDataModel messages land, and two turns later the agent swaps day two entirely.
That's not one spec becoming one component tree โ it's a surface being edited over time, and that's exactly what the envelope stream models.
Now a form the agent needs back.
A valid submit goes back to the agent as a structured action message on its own; create the surface with sendDataModel and the current data model rides along.
Either way, user input flows back through the protocol, not through handlers you wire yourself.
So the reasons to step up: incremental surfaces, data arriving separately from structure, and user actions as first-class protocol messages.
The honest cost is protocol discipline. Envelopes have to be valid and arrive in a sensible order, the catalog has to support every component type the agent names, and someone has to actually design the action semantics โ a fixed spec asks for none of that.
What does it cost to switch?
Inside chat, less than you'd think.
Let's look at why. The same [views] catalog feeds both paths, and chat detects the contract from the first bytes: { means a json-render spec, ---a2ui_JSON--- means A2UI JSONL.
So the choice is per-surface, not per-app โ and it's revisable.
For me, the default is: start with json-render, and step up to A2UI only when a surface genuinely needs to live past its first render. You're not locked in either way, so the cheap contract is the right place to begin.
Conclusion
The heuristic is short: if you can validate the entire UI before it renders, start with json-render; if the surface keeps changing after it lands โ data trickling in, actions coming back, edits across turns โ use A2UI.
The mechanical comparison has the layer-by-layer details, the generative UI guide wires up the json-render path end to end, and the A2UI overview does the same for surfaces. Pick a surface you're building this week, run it up the ladder, and let me know where it lands.