Generative UI¶
Agents can answer with interface, not only text. AI Elements supports two open formats:
- A2UI v1.0 surfaces, rendered natively with Compose (
ai-elements-genui); - MCP Apps views of MCP tools, rendered in a sandboxed WebView
(
ai-elements-mcp-apps).
A2UI¶
A2UI surfaces arrive as DataPart.A2UI whichever protocol carried them: AG-UI a2ui-surface
activities, A2A application/a2ui+json parts, or AI SDK data-a2ui parts. User actions go back on
the same binding (forwardedProps.a2uiAction, an A2UI A2A part, a data-a2ui part).
Register the renderer:
CompositionLocalProvider(
LocalAiElementsRenderers provides AiElementsRenderers(
data = mapOf(DataPart.A2UI to a2uiRenderer { action -> controller.send(action) }),
codeBlocks = mapOf("jsx" to jsxCodeBlocks { action -> controller.send(action) }), // ```jsx fences → JsxPreview
),
) {
Chat(controller)
}
controller.send(action) is an extension in dev.ai.elements.genui.a2ui: it sends the action's
user message together with its data parts.
- Catalogs. The Basic Catalog is built in. Add your design system with
A2uiCatalog.Basic.extend(id = "https://example.com/catalog", components = …). - State. A form keeps what the user typed while more messages stream in and while it scrolls out of sight: sessions live in the renderer, not in the lazy list.
- Outside the chat.
A2uiSurfaceViewandA2uiStaterender a surface anywhere. - Lifecycle. A part holding
{"status": "building" | "retrying" | "failed"}(the AG-UI middleware's pre-paint lifecycle) shows as a placeholder.
The reference server's hotel concierge (hotel) answers with an
A2UI booking form on AG-UI, AI SDK and A2A.
JSX¶
JsxPreview renders JSX (the AI Elements JSXPreview) natively: the JSX compiles onto A2UI
components and renders with the same catalog as A2UI surfaces, so it looks like the rest of the app.
It evaluates data only, never code, and renders while the JSX streams in.
JsxPreview(
jsx = source, // what the model wrote
bindings = buildJsonObject { put("email", "") }, // the data model {name} reads
onAction = { action -> controller.send(action) }, // onClick={subscribe} → "subscribe"
)
In a chat, jsxCodeBlocks renders ```jsx (and ```tsx) fences in answers as live
previews, with a toggle to show the source. Register it with
AiElementsRenderers(codeBlocks = mapOf("jsx" to jsxCodeBlocks(), "tsx" to jsxCodeBlocks())).
What it understands¶
| JSX | Renders as |
|---|---|
h1–h6, p, span, b / strong, i / em, code, small |
Text (headings and inline styles as Markdown) |
div, section, main, … (className="flex …" or flex-row makes a Row) |
Column / Row |
ul / ol with li |
a bulleted or numbered Column |
hr |
Divider |
img src alt |
Image |
a href |
a link button (opens the URL) |
button / <Button variant> |
Button, with onClick as its action |
input (type text, email, password, number), textarea |
TextField; name="x" binds it to /x |
input type="checkbox" checked={x} |
CheckBox |
select name with option value |
ChoicePicker (multiple for several) |
<Tabs> with <Tab title> |
Tabs |
Any Basic Catalog component by name: Card, Row, Column, List, Text, Image, Icon, Video, AudioPlayer, Slider, CheckBox, ChoicePicker, DateTimeInput, TextField, Modal, … |
that component, with its properties |
Unknown tags (<Fragment>, <section>, …) |
their children |
Values¶
- Bindings.
{name}or{a.b}read the data model (bindings). Inputs bound to a path write back into it, locally, and an action carries the current data model. - Literals. Strings, numbers,
true/false, and JSON arrays and objects (options={[{"label": "S", "value": "s"}]}). - Nothing else runs.
{alert(1)}, arrow functions and other code are dropped, never evaluated: model output cannot execute in the app. - Actions.
onClick={save}oronClick="save"sends the actionsavetoonAction.
While it streams¶
Half-written tags wait until they close, and unclosed tags close at the end. The preview keeps one surface for the whole stream, so what the user typed stays while more JSX arrives.
See every JSX sample, with its source, under Generative UI in the component catalog. In the demo app, tap Generative UI (JSX form): the offline agent answers with a JSX booking form.