
You ask a question. You get back text. You still have to read it, decide, and go tap the real UI yourself.
Every screen drawn, every route wired by hand. The journey can only go where you predicted it would.
You register the widgets; the model composes the screen for what was actually asked — and rebuilds it as the conversation moves.
Instead of replying in text, the model assembles real, interactive widgets — cards, chips, forms — out of your design system, shaped by what the person actually asked for.
They get a screen to use. Not a transcript to read.
An experimental package from the Flutter team. It lets the model build your app's UI at runtime — from widgets you own, in your brand.
You decide how everything looks. The model decides what to show.
⚠︎ It's early. The API still changes between versions — pin your version and expect to refactor.
The model can only use widgets you register. Not in the catalog? It can't use it. Your design system, enforced at runtime.
The model describes the UI as data. Your app turns that into real, native Flutter widgets.
A tap or a pick goes back to the model, which updates the screen. That loop is what makes it generative, not just dynamic.
The model never runs code on your client. It sends data describing pre-approved components — nothing to inject, nothing to execute.
That JSON maps to your widgets. Your fonts, spacing, theme. It looks like your app because it is your app.
Flat JSON arrives piece by piece, so the UI builds as the model thinks — no waiting for one perfect blob.
Underneath sits an open protocol. The same response can render on Flutter, React, Angular, or native — each drawing with its own components.
Apache-2.0, built by Google with the open-source community. Flutter is one renderer that speaks it.
A2UI lets a model send an interface safely across a trust boundary: not text, not code, but a declarative description the client renders with its own widgets — natively across web, mobile, and desktop.
Each turn, the screen gets closer to what they meant.
A cooking assistant. Tell it what you feel like — "something quick with paneer" — and it builds the screen: preference chips, recipe cards, a full recipe you can tweak. Plain words in, a usable screen out.
One thing to hold onto: the app owns the facts, the model owns the words.
Watch the screen rebuild itself each turn. Nobody designed these exact states — the model assembled them from my widgets.




It chose from the recipes I gave it, and adapted only the language — the steps, the tone. The cook times and ingredients came straight from my data.
That split — model for words, app for facts — is the quiet trick behind the whole thing.
Follow one turn, top to bottom — each piece hands off to the next, then loops back.
final recipeCardItem = CatalogItem(
name: 'RecipeCard',
// The schema only accepts an id — not a title, not a price.
// The model can point at a recipe, never invent one.
dataSchema: S.object(properties: {
'recipeId': S.string(),
'reason': S.string(), // the one thing it may write freely
}, required: ['recipeId']),
widgetBuilder: (ctx) {
final recipe = recipeById(ctx.data['recipeId']);
return RecipeCard(recipe: recipe); // your widget, your brand
},
);
name + schema + builder. The schema is the leash — it's how you stop the model hallucinating recipes that don't exist.
final recipe = recipeById(recipeId); // FACTS: from my data
final modelSteps = data['steps']; // WORDS: from the model
// Use the model's adapted steps if it sent any —
// e.g. "no blender" — otherwise fall back to mine.
final steps = (modelSteps != null && modelSteps.isNotEmpty)
? modelSteps
: recipe.steps;
The model rewrites the phrasing. The cook time, the ingredients, the image — those are never up for negotiation.
// No API key in the app — Firebase AI Logic holds it.
_model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-3.1-flash-lite',
systemInstruction: Content.system(buildSystemInstruction()),
);
// genui just needs streamed text. Feed each chunk in.
await for (final res in chat.sendMessageStream(content)) {
_adapter.addChunk(res.text); // the backend-agnostic seam
}
genui doesn't care which model you use. Change providers later and this is the only file you touch.
Four things I learned building this — the kind you only find out by shipping.
Locking the model to your data kills hallucination. It also means the moment someone asks for something you don't have, it has nothing honest to say.
Ask for a burger from a paneer menu, and a naive build will force paneer on you. You have to teach it to say "I don't have that — here's what I can do."
The model is wonderful with language and careless with facts. So don't ask it to remember your prices or your inventory.
Let it own the words. Let your app own the facts. Once you draw that line, a lot of scary failure modes just… disappear.
Generative UI keeps adding. Nothing gets tidied unless you tidy it. And the model controls each screen's identity — a smaller model that reuses an id will quietly paint over your last one.
Model quality isn't abstract here. It shows up directly in your UX.
One thing the person does can be several model calls — gather, show, refine. So you hit rate limits sooner than a plain chat app would.
Pick your model tier with that in mind, and design your "thinking…" states for real.
Sage is my weekend toy. But this isn't a lab trick — over the past year, real teams have shipped real GenUI apps.
Three worth knowing: one from the Flutter team, one from a top agency, and one from a solo GDE.
The Flutter team's own GenUI demo. A couple of playful taps, and Gemini composes a personalized latte — Dash dreams it up while the screen assembles itself.
Very Good Ventures' AI financial planner, built for Google Cloud Next 2026. Ask about a goal — “retire by 50?” — and it renders sliders, charts and chips on the fly. The model builds the screen, not a paragraph.

A language-learning app by Flutter GDE Cagatay Ulusoy. Each lesson's UI is generated on the fly to fit the drill — and it was shown live in the Google I/O “What's new in Flutter” session.

When the screen should change with intent — exploring, deciding, narrowing down, or taking in a photo / voice / messy input.
A static form. A settings page. A fixed flow you already know. A good plain screen still wins more often than people admit.
A chatbot that builds UI isn't always the answer. Use it where the uncertainty lives.







akanshajain.dev
LinkedInI help organise Flutter Delhi and FFDG New Delhi — talks, meetups, and a room full of people figuring this out together. Scan in, say hi.
Or DM me after — I read every message.