EngineeringOpens this cluster

Generative UI on native mobile: how an agent draws real product UI

Generative UI on the web streams React components. On iOS and Android it has to become a typed spec over a fixed native catalogue. Here is why, and how it works.

VVoqal · Engineering15 min read

About this article

Ask an assistant for your account balance and it can reply with a sentence. Ask it inside your banking app and a sentence is the wrong answer, because your app already knows how to draw a balance. It has a view for that, with the right typeface, the right currency formatting, a tap target that meets your accessibility bar, and a follow-up chip that opens the transaction list.

Generative UI is the name for letting the agent pick that view. On the web the pattern is settled and documented. On iOS and Android it has almost no written account at all, which is odd, because the constraint that makes it interesting is a mobile constraint.

What generative UI means, and what changes on native#

Generative UI means the model decides which interface to show, not only what text to emit. Vercel’s AI SDK gives the tightest definition available: it is “the process of connecting the results of a tool call to a React component”. The model calls a tool, the tool returns data, and a component renders that data instead of a paragraph describing it.

The definition travels. The implementation does not. A browser can receive code it has never seen and run it. An iOS or Android app cannot: the binary was compiled, signed and reviewed before the conversation started. So on native, the agent cannot send a view. It can only send instructions for choosing and filling a view that is already there.

That single constraint produces a different architecture, and a better security story as a side effect.

The two steps that do not exist in the web version are the ones inside the runtime: choose a kind from the catalogue, then refuse anything that is not in it.

The web answer, and why it does not port#

There are two mature web implementations of this idea, and a careful reading of both explains why neither reaches a native app.

Streaming components assumes a runtime that can evaluate them#

Vercel’s server-streamed variant, AI SDK RSC, sends React server components down the wire as the model produces them. It is worth noting what Vercel says about it in their own docs: “AI SDK RSC is currently experimental. We recommend using AI SDK UI for production.” The production-recommended path keeps components in the client bundle and streams only tool state, using three documented states, input-available, output-available and output-error.

Even the production path assumes a JavaScript runtime that can be handed new component trees. Ship that to an App Store binary and you are shipping an interpreter. Apple’s review rules have opinions about that, your binary grows, and your offline behaviour becomes a second implementation of everything.

HTML in an iframe is not a native interface#

The MCP Apps extension takes the other route, and it is a serious piece of work. Its overview opens with the right problem statement: “Text responses can only go so far. Sometimes users need to interact with data, not just read about it.” The mechanism is a tool description carrying a _meta.ui.resourceUri, a ui:// resource holding an HTML page, and a host that mounts it. The spec, versioned 2026-01-26, is explicit about where that page runs: “MCP Apps run in a sandboxed iframe controlled by the host. They can’t access the parent page, steal cookies, or escape their container.”

An iframe is a sound answer for a chat host that is already a browser. Inside a native app it reintroduces the web layer you left, with its own scroll physics, its own font stack, its own accessibility tree and its own offline story. CopilotKit, who build on the same handshake, write the limitation into their own comparison page: MCP Apps is “Web-first: not yet a good fit for non-web surfaces such as mobile or Slack.”

The evidence that the pattern is worth the trouble#

Two 2026 papers make the case that this is worth solving rather than abandoning. Chen and colleagues, in Generative Interfaces for Language Models (ACL 2026 Findings), report that “generative interfaces consistently outperform conversational ones, with up to a 72% improvement in human preference.” Keep the “up to” attached; it is their hedge and it is doing work.

The more useful paper is Google’s, because of what it concedes. In Generative UI: LLMs are Effective UI Generators (24 February 2026), Leviathan and colleagues report that “when ignoring generation speed, results generated by our implementation are overwhelmingly preferred by humans over the standard LLM markdown output”, and then, in the next sentence: “while the results generated by our implementation are worse than those crafted by human experts, they are at least comparable in 50% of cases.”

Read those two sentences together and you have the whole design argument. Generated interfaces beat walls of text. Human-designed interfaces beat generated ones. The right place to land is the combination: interfaces your designers built, chosen at runtime by the model.

The native answer: a closed catalogue and a typed spec#

The mobile version of generative UI has two halves. The app ships a fixed catalogue of views. The agent emits a typed spec that names one of them and fills it.

Nothing executable crosses the wire. A payload is a small JSON object with a kind field that selects a view and a handful of data fields that populate it. Every view in the catalogue was written by your team, in Swift or Kotlin or Dart, reviewed the way the rest of your app is reviewed, and compiled into the binary. The model’s freedom is real but bounded: it decides which of your views the answer deserves and what goes in it.

A contact sheet showing every render-spec widget drawn at once, including stat, progress, list, record, chart, confirm, products, ticket, callback, rating, text and media.
Voqal's catalogue drawn in one sheet. This is the entire vocabulary an agent gets to choose from, and it is finite by design. It is a contact sheet from the React widget rendering harness on invented sample data, not a capture of the iOS SDK, so read the shapes rather than the figures.

This is also the answer to the question people ask next, which is how it differs from server-driven UI. It does not differ much, and that is the point: server-driven UI already has a decade of mobile practice behind it, and Thoughtworks put it in the Trial ring of its Technology Radar in Volume 34, April 2026, noting that “by separating rendering into a generic container while providing structure and data via the server, mobile teams can bypass lengthy app store review cycles for every iteration.” Generative UI on native is server-driven UI where the server is an agent. The schema work you would have done anyway is the same schema work, and it matters more, because the thing filling the schema is now probabilistic.

Walking one payload from model to native view#

Here is a real shape, trimmed to its skeleton. The model has called a balance tool, received rows, and now has to decide how to present them.

json
{
  "kind": "stat",
  "title": "Available balance",
  "value": "EGP 48,250.00",
  "caption": "Next settlement Thursday",
  "actions": [
    { "label": "Show transactions", "prompt": "show my last transactions" }
  ]
}

Four fields and a small array. kind selects the view type. The rest fill it. actions becomes follow-up chips, each carrying the prompt it would send, so a tap continues the conversation rather than deep-linking out of it.

The arrow labelled 'selects' is the only one that changes structure. Every other field is content poured into a shape the app already decided.

On iOS that resolves to a SwiftUI view the app compiled months ago. The model never learns SwiftUI, never emits a layout, and never chooses a font size. It chooses a widget kind.

One model reply splitting at a render separator into a spoken answer and a JSON widget array, which becomes a native card
Animation: one reply, two channels. The separator is where the spoken answer stops and the widget array begins, and the app draws the card from the second half alone.

And here is the same mechanism having actually run, on a device, against Paymob’s backend. The account is the shared test merchant we develop against, so every figure comes back zero.

The Voqal iOS SDK answering a balance question inside a payments app, showing a spoken answer headline, a balance breakdown card reading EGP 0.00 on every row, and two follow-up chips above the voice dock.
The card is a native view; the agent supplied the kind and the numbers. The account is the shared test merchant, which is why all three figures are zero, and the zeros are the point: the widget draws whatever the backend returned.

The same catalogue, in a different product, produces something that looks nothing like a balance card, because a different set of views was registered.

The Voqal iOS SDK answering a grocery request with a products widget listing catalogue items with images, sizes, prices and add-to-basket steppers.
A grocery tenant on the same contract. One spec format, two products, and neither app wrote assistant UI.

The five objections, answered#

The objections to generative UI are good ones, and they are already written down. The best collection is a Hacker News thread from December 2025 titled “What Is Generative UI?”, where people who build interfaces for a living explain what worries them. Every one of those objections is an objection to free-form generation. A catalogue answers them individually.

Consistency#

The sharpest version comes from the commenter bccdee: “Personalized interfaces are bad. I don’t want to configure anything, and I don’t want anything automatically configured on my behalf.” They go on: “Your UI should be clear and predictable. A chatbot should not be moving around the buttons.”

This is correct, and a catalogue does not move the buttons. The balance card renders identically for every user who asks about their balance. What varies between turns is which card appears, in the same way that tapping a different tab shows a different screen. The user is choosing, by asking.

Reproducibility and support#

Also from that thread, tartoran: “You won’t remember how you got there or did what because there are n permutations of getting there or doing that, except they’re vaguely similar but not exactly the same thing.” And the support consequence, imagined by marcyb5st as a line from a future support call: “Yeah, I cannot reproduce your issue because things on my end look different”.

With a closed catalogue, n is not unbounded: it is the number of widget kinds, and it is printed in your docs. A support engineer reproduces a session by replaying the same spec, because the spec is logged, small, and fully determines the render. That is strictly easier to reproduce than a hand-built screen whose state came from six sources.

Documentation#

You cannot document an interface that is generated fresh each time. You can document thirteen widget kinds, their fields, and the conditions under which the agent picks each one, because that is a finite list that changes when you change it. The widget catalogue is a documented artefact, reviewed like an API, and versioned like one.

UI impersonation and injection#

This is the objection with teeth, and it is the one where the native answer is strongest rather than merely adequate. Greshake and colleagues showed in Not what you’ve signed up for (February 2023) that “processing retrieved prompts can act as arbitrary code execution, manipulate the application’s functionality, and control how and if other APIs are called”, demonstrated against real systems including Bing’s GPT-4 powered Chat.

If the model’s output is markup, an injected instruction can produce a convincing fake login prompt. If the model’s output is a kind from a fixed list plus data fields, the worst it can produce is a real widget containing wrong text. Someone made this argument on the A2UI thread in December 2025, independently of any vendor, the commenter epec254: “Most HTML is actually HTML+CSS+JS - IMO, accepting this is a code injection attack waiting to happen. By abstracting to JSON, a client can safely render UI without this concern.”

The rebuttal on that same thread deserves quoting too, from lunar_mycroft: “If the JSON protocol in question supports arbitrary behaviors and styles, then you still have an injection problem even over JSON.” That is exactly right, and it is why the catalogue has to stay closed. A spec format with an escape hatch for arbitrary styling has quietly become a markup language with extra steps. The defence is the constraint, not the file format. What the catalogue is actually buying you, in full, is a longer argument than fits here.

What happens when the model asks for a widget you do not have#

It asks. Models generalise, and sooner or later yours will emit "kind": "seat_map" because a seat map is obviously what that answer wanted. The handling has to be decided before it happens.

Validation belongs on the server, before the payload is sent. A device that has never seen an unknown kind cannot mishandle one.

The spoken answer still arrives, so the turn is not lost. And the rejected kind is logged, which turns a failure into a roadmap: the widgets your agent keeps reaching for are the widgets your catalogue is missing.

The left side is not more powerful in practice, it is only harder to promise anything about. Accessibility guarantees like WCAG's 24 by 24 pixel target size can be enforced once on the right and never again.

That last point is worth making concretely. WCAG 2.2, a W3C Recommendation since 12 December 2024, requires under success criterion 2.5.8 that pointer targets be “at least 24 by 24 CSS pixels”. You can satisfy that once, in twelve views, and know it holds for every answer the agent ever gives. You cannot promise it for markup a model wrote thirty seconds ago.

What this is not#

It is not the model writing your app. Nothing about this pattern generates Swift, generates layout code, or edits your project. The catalogue is designed by designers and built by engineers on the normal schedule.

It is also not a way to skip design work. Google’s own result says human experts still produce the better interface. What changes is when the choosing happens: at runtime, per question, instead of at build time, per screen. Your team still decides what a good balance card looks like. The agent decides that this question deserves one.

Nor is it finished as an industry. Flutter’s first-party attempt is honest about its status: the genui package docs, updated 19 August 2026, state that “the genui package is in alpha and is likely to change.” The specs are young, the protocols disagree with each other, and anyone claiming this is settled is selling something. What is already true is that the native constraint has a good answer, and that answer is older and duller than the web’s: define a schema, keep it closed, and let the agent fill it, which is the settled part of a decade of server-driven-UI practice rather than anything the last two years invented. Deciding what the user sees during the second before the spec arrives is a separate problem, and a surprisingly large share of the felt quality. So is the shape of the JSON contract itself, which is where most of the design arguments actually happen.

Two more pieces sit either side of the contract. The server-driven UI case stated on its own terms, costs included, is the argument to read if you are being asked to justify the schema to someone who did not want it. And a search answer assembled from a whole sentence is the case where the temptation to let the model lay out the page is strongest, and where a closed catalogue earns its keep most visibly.

Common questions#

What is generative UI?#

Generative UI is a model choosing the interface as well as the content of a reply. Instead of answering in prose, it selects a component or a widget appropriate to the answer and fills it with the data a tool call returned.

How is generative UI different from server-driven UI?#

They are the same mechanism with a different author. Server-driven UI has a backend send a structured description of a screen that the client renders from views it already has. Generative UI has a model produce that description. The client contract, and most of the engineering difficulty, is identical, which is why the decade of server-driven-UI practice linked earlier is the most useful literature available.

Can an LLM generate native iOS or Android UI, not just HTML?#

Not directly, and it should not try. A signed binary cannot evaluate views it was not compiled with. What works is a typed spec: the model emits JSON naming a widget kind from a fixed catalogue, and the app draws the matching native view. The result is native, and the model wrote no view code.

Is generative UI safe? What stops the model rendering something malicious?#

With free-form markup, nothing does, and indirect prompt injection is a demonstrated attack against real deployed systems. With a closed catalogue the model cannot author markup at all. It can only name a kind from a list and supply data fields, so the worst outcome is a genuine widget showing wrong text, which your confirm gate is there to catch before anything is written.

How do you keep a generated interface consistent enough to support?#

By making the set of possible renders finite and logging the spec. Every balance answer produces the same balance card. A support engineer replays the logged spec and sees exactly what the user saw, because the spec fully determines the render.

Sources#

Filed underServer-driven UIRender specWidgetsAgents

Next

The catalogue of every widget kind the agent can ask your app to draw, and the payload each one takes.

See every widget kind the agent can render

The rest of Render spec

Open the cluster

Server-driven native UI: the agent names the widgets, your app draws them, and shipping a new answer shape stops requiring an app release.

Elsewhere on the map