{"slug": "day-3-generative-ui-gen-2-declarative-specs-with-a2ui", "title": "Day 3: Generative UI Gen 2 — Declarative Specs with A2UI", "summary": "Google released A2UI (Agent-to-User Interface) in late 2025, an open protocol that enables LLMs to compose user interfaces declaratively rather than selecting pre-built components. The protocol transmits UI as a stream of JSON Lines, where agents send abstract component trees and data separately, allowing clients to render native widgets across platforms while keeping agent-generated code behind trust boundaries. A2UI uses a client-defined catalog of allowed component types, enabling the same JSONL stream to render as React components on web, Flutter widgets on mobile, or SwiftUI views on iOS.", "body_md": "*This is Day 3 of my 6-part series on how LLMs rewrote the user interface over the past year. Day 2 covered static generative UI with AG-UI — and ended on its core limitation: the agent can only show what you've pre-built.*\n\nGen 1's contract was \"pick from my components.\" Gen 2's contract is more interesting: **\"compose a UI from my primitives.\"**\n\nThe agent doesn't return code, and it doesn't just pick a component — it returns a *declarative description* of an interface: a tree of abstract components (cards, rows, text, inputs) plus the data to fill them. Your client renders that description using its own native widgets. The agent decides *structure*; you still decide *implementation, styling, and what's allowed*.\n\nThis is the pattern behind **A2UI** (Agent-to-User Interface), the open protocol Google released in late 2025. It came out of a real constraint: Google needed agents to send rich UI across *trust boundaries* — a third-party agent rendering inside Gemini, say — where executing agent-generated code is a non-starter.\n\nA2UI transmits UI as a stream of JSON Lines. Each line is one message; the client builds the interface incrementally as lines arrive — so the UI paints progressively, like streamed text, instead of popping in at the end.\n\nFour server-to-client message types do all the work: `surfaceUpdate`\n\n(add/update components), `dataModelUpdate`\n\n(set data), `beginRendering`\n\n(signal first paint with the root id), and `deleteSurface`\n\n(remove a UI region).\n\nHere's an actual minimal stream that renders a profile card:\n\n```\n{\"surfaceUpdate\": {\"components\": [{\"id\": \"root\", \"component\": {\"Column\": {\"children\": {\"explicitList\": [\"profile_card\"]}}}}]}}\n{\"surfaceUpdate\": {\"components\": [{\"id\": \"profile_card\", \"component\": {\"Card\": {\"child\": \"card_content\"}}}]}}\n{\"surfaceUpdate\": {\"components\": [{\"id\": \"card_content\", \"component\": {\"Column\": {\"children\": {\"explicitList\": [\"name_text\", \"bio_text\"]}}}}]}}\n{\"surfaceUpdate\": {\"components\": [{\"id\": \"name_text\", \"component\": {\"Text\": {\"usageHint\": \"h3\", \"text\": {\"literalString\": \"A2A Fan\"}}}}]}}\n{\"surfaceUpdate\": {\"components\": [{\"id\": \"bio_text\", \"component\": {\"Text\": {\"text\": {\"literalString\": \"Building beautiful apps from a single codebase.\"}}}}]}}\n{\"dataModelUpdate\": {\"contents\": {}}}\n{\"beginRendering\": {\"root\": \"root\"}}\n```\n\nTwo design choices worth noticing:\n\n**It's an adjacency list, not a nested tree.** Components reference children by id (`\"children\": {\"explicitList\": [...]}`\n\n) instead of nesting. That's deliberate: flat structures are easier for an LLM to generate correctly token-by-token, and easier to stream — the spec's first design requirement is literally \"must be easily generated by a Transformer LLM.\"\n\n**Structure and state are decoupled.** Components describe *shape*; the data model holds *values*. Update a price? Send a tiny `dataModelUpdate`\n\n— no need to resend the component tree. Bindings connect the two:\n\n```\n{\"surfaceUpdate\": {\"components\": [\n  {\"id\": \"price_text\", \"component\": {\"Text\": {\"text\": {\"path\": \"/flight/price\"}}}}\n]}}\n{\"dataModelUpdate\": {\"contents\": {\"flight\": {\"price\": \"$267\"}}}}\n```\n\nWhen the user taps a button, the client doesn't run agent code — it sends a `userAction`\n\nmessage back to the agent (`error`\n\nis the only other client-to-server type). The agent responds with new stream messages. The loop is: agent streams UI → user acts → event goes back → agent streams an update. The primary data stream stays unidirectional, which keeps the security story clean.\n\nThe piece that makes A2UI deployable across platforms is the **catalog** — a client-defined contract listing which component types it can render (`Card`\n\n, `Row`\n\n, `Text`\n\n, `DateTimeInput`\n\n, …) and their properties. The agent can only reference types in the catalog. Anything else is rejected.\n\nThis is also the cross-platform answer Gen 1 lacked. The same JSONL stream renders as React components on web, Flutter widgets on mobile, SwiftUI views on iOS — each client maps abstract types to its own native widgets via its registry. Write the agent once; every surface renders it natively. There's a standard catalog per protocol version, and you can negotiate custom ones (the server advertises supported catalogs in its A2A Agent Card; the client declares what it accepts).\n\nSecurity falls out of the design: A2UI is data, not code. No script execution, no UI injection beyond what your catalog allows, no iframe of mystery origin. For regulated industries, that sentence is the whole pitch.\n\nCompared to Gen 1, you trade some safety guarantees for a lot of expressiveness:\n\n| Gen 1 (AG-UI static) | Gen 2 (A2UI declarative) | |\n|---|---|---|\n| Agent controls | Which component + props | Full UI structure from primitives |\n| You control | Everything else | Catalog, rendering, styling |\n| Novel layouts | No — prebuilt only | Yes — composed at runtime |\n| Cross-platform | Re-implement per client | Same stream, native render everywhere |\n| Failure mode | Weird data in a good component | Weird layout from good components |\n\nThat last row is the honest caveat: when the agent composes structure, it can compose *bad* structure — a form with the submit button above the fields, a 14-component card where 4 would do. Your catalog constrains vocabulary, not taste. Teams shipping Gen 2 typically add layout linting or few-shot examples of good composition in the agent prompt.\n\nThe deeper limit: the agent is still confined to your primitives. It can't ship an interactive chart type you never built, or a custom visualization for a domain it just learned about. For that you need to hand the agent a real, sandboxed UI surface — which is Gen 3, MCP Apps, and tomorrow's post.\n\nIf you want to explore A2UI today: the spec, quickstart, and a live composer are at [a2ui.org](https://a2ui.org), and the project is open source on GitHub. See you tomorrow.", "url": "https://wpnews.pro/news/day-3-generative-ui-gen-2-declarative-specs-with-a2ui", "canonical_source": "https://dev.to/ravidasari/day-3-generative-ui-gen-2-declarative-specs-with-a2ui-34eg", "published_at": "2026-06-12 02:53:13+00:00", "updated_at": "2026-06-12 03:42:24.601265+00:00", "lang": "en", "topics": ["large-language-models", "generative-ai", "ai-agents", "ai-products", "ai-tools"], "entities": ["A2UI", "Google", "Gemini", "AG-UI"], "alternates": {"html": "https://wpnews.pro/news/day-3-generative-ui-gen-2-declarative-specs-with-a2ui", "markdown": "https://wpnews.pro/news/day-3-generative-ui-gen-2-declarative-specs-with-a2ui.md", "text": "https://wpnews.pro/news/day-3-generative-ui-gen-2-declarative-specs-with-a2ui.txt", "jsonld": "https://wpnews.pro/news/day-3-generative-ui-gen-2-declarative-specs-with-a2ui.jsonld"}}