Rendering with Vue
How a consuming Vue app turns streamed A2UI messages into live MeldUI components with the @meldui/a2ui/vue reference renderer — provideA2UI, processMessages, and A2UISurface.
@meldui/a2ui ships in two halves:
@meldui/a2ui— the portable, framework-agnostic catalog contract an agent targets (schema + component definitions). Covered in the Overview.@meldui/a2ui/vue— the Vue reference renderer that turns streamed A2UI v0.9 messages into live@meldui/vuecomponents.
This page is about the second half: how a consuming Vue app wires up the renderer and feeds it the messages an agent emits. It’s built on Google’s @a2ui/web_core with fine-grained, per-component reactivity, so only the components whose data changed re-render.
Install
pnpm add @meldui/a2ui @meldui/vue vue
# optional — only if your surfaces use the Icon or Chart components:
pnpm add @meldui/tabler-vue @meldui/charts-vue
@meldui/vue and vue are peer dependencies: the renderer maps catalog components onto your installed @meldui/vue, so the surface looks exactly like the rest of your app.
Set up styles
A2UI surfaces are plain @meldui/vue components, so they inherit your app’s MeldUI theme through the normal CSS cascade — there’s no separate “A2UI theme” to wire up. If you’ve already followed the Installation guide, you’re done. Otherwise, import the MeldUI tokens once in your Tailwind v4 entry CSS:
@import 'tailwindcss';
@import '@meldui/vue/themes/default';
/* Let Tailwind see the classes the renderer's components use */
@source "../node_modules/@meldui/vue/dist/**/*.mjs";
The Markdown component (the primary path for streamed agent text) also needs its prose styles, imported once anywhere in your app:
import '@incremark/theme/styles.css'
Wire up the renderer
Three steps, all in one root component:
- Call
provideA2UI()insetup()— it creates a message processor and provides it to the subtree. - Feed streamed messages to
processor.processMessages(...)as they arrive from your transport. - Mount
<A2UISurface :surface-id="…">wherever the surface should appear.
<script setup lang="ts">
import { onMounted } from 'vue'
import { provideA2UI, A2UISurface } from '@meldui/a2ui/vue'
const { processor } = provideA2UI({
// Forward client actions (e.g. a Button press) back to your agent.
onAction: (action) => sendToAgent(action),
})
onMounted(() => {
// `incomingMessages` come from your transport — SSE, WebSocket, or an A2A stream.
processor.processMessages(incomingMessages)
})
</script>
<template>
<A2UISurface surface-id="main" />
</template>
<A2UISurface> must be rendered inside a component that called provideA2UI() (it injects the processor); otherwise it throws. A surface doesn’t need to exist when the component mounts — it resolves reactively when its createSurface message arrives, so you can mount the surface before the first message lands.
What the messages look like
The agent streams A2UI v0.9 messages. A minimal single-surface sequence is createSurface → updateComponents (with an optional updateDataModel for bound values). The surfaceId ties the messages to the <A2UISurface surface-id> you mounted, and catalogId is the MeldUI catalog id:
[
{
"version": "v0.9",
"createSurface": {
"surfaceId": "main",
"catalogId": "https://meldui.dipayanb.com/a2ui/v1/catalog.json"
}
},
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "main",
"components": [
{ "id": "root", "component": "Card", "child": "col" },
{ "id": "col", "component": "Column", "children": ["t1", "t2"] },
{ "id": "t1", "component": "Text", "text": "Card title", "variant": "h4" },
{
"id": "t2",
"component": "Text",
"text": "Card body content goes here.",
"variant": "body"
}
]
}
}
]
Components reference each other by id (child / children), with root as the surface entry point. You can call processMessages repeatedly as chunks stream in — later updateComponents and updateDataModel messages patch the surface in place, and only the affected components re-render. Here’s that exact pattern rendered live, with the messages an agent would emit in the Code tab:
Handling actions
Interactive components (a Button, a form control) dispatch a client action when the user interacts with them. Provide an onAction handler to forward it to your agent:
provideA2UI({
onAction: (action) => {
// action.name — the event name the agent declared, e.g. "save"
// action.sourceComponentId — which component fired it
// action.surfaceId — which surface it belongs to
// action.context — the relevant slice of the surface's data model
sendToAgent(action)
},
})
In the catalog, a button declares its event inline — "action": { "event": { "name": "save" } } — and that name is what arrives as action.name.
Data binding
Inputs two-way bind to the surface’s data model rather than to local component state. A field points at a path with value: { "path": "/name" }, and an updateDataModel message seeds (or updates) it:
[
{
"version": "v0.9",
"createSurface": {
"surfaceId": "main",
"catalogId": "https://meldui.dipayanb.com/a2ui/v1/catalog.json"
}
},
{
"version": "v0.9",
"updateDataModel": { "surfaceId": "main", "path": "/", "value": { "name": "Ada" } }
},
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "main",
"components": [
{
"id": "root",
"component": "TextField",
"label": "Your name",
"value": { "path": "/name" }
}
]
}
}
]
Edits in the rendered input flow back into the data model, and that bound state is what shows up in action.context when the user submits — so the agent always sees the current values without you threading state by hand.
Restricting the catalog
By default the renderer registers all 35 catalog components. To allow only a subset — for a constrained surface, or to keep an agent on a smaller, more reliable vocabulary — pass your own list:
import { provideA2UI, meldVueCatalog } from '@meldui/a2ui/vue'
const allowed = meldVueCatalog.filter((c) =>
['Card', 'Column', 'Text', 'Button', 'TextField'].includes(c.name),
)
provideA2UI({ catalog: allowed })
Every entry you pass must be a real component in the published @meldui/a2ui contract — the renderer validates this on setup and throws if a component isn’t in the catalog, so the renderer can never drift from the agent-facing contract.
Multiple surfaces
One provideA2UI() host can drive many surfaces. Mount a <A2UISurface> per surfaceId the agent creates — for example a main panel and a modal:
<template>
<A2UISurface surface-id="main" />
<A2UISurface surface-id="dialog" />
</template>
Each surface renders independently and resolves as its createSurface message arrives.
Theming
Surfaces use MeldUI’s semantic OKLCH tokens through their Tailwind classes, so they automatically follow your app’s light/dark palette — no bridge code, and the agent’s optional theme.primaryColor is intentionally not applied so surfaces stay visually consistent across agents. See A2UI Theming for token overrides and per-component styling.
See also
- A2UI Overview — the catalog contract and how agents negotiate it.
- Catalog Reference — every component, its props, and the functions.
- Playground — paste raw v0.9 messages and watch them render.
- Gallery — complete surfaces with the exact messages behind them.