Skip to main content
// A2UI → BUBBLE TEA BRIDGE

render agent UIs
in the terminal

a2tea parses the A2UI an agent emits — interleaved with prose in an LLM reply — and draws the described surfaces as Bubble Tea models. JSON in, a live TUI out.

GoA2UI v0.9lipgloss/v2Apache-2.0
~/kyoto — crush
$ crush "plan a weekend in kyoto"
Here's an option for your trip —
Kyoto · Autumn Weekend
3 nights · ¥182,000 · 2 travelers
[ Book it ]Adjust dates
tab cycle • enter select • q quit
a2tea turns an A2UI surface into a focusable Bubble Tea model.
// WHAT IT DOES

a real renderer, not a mock

Everything a host needs to recognize A2UI in a model's reply and draw it — detection, real Lip Gloss rendering, and the first wired interaction round-trip.

Detect A2UI in any reply

Contains and Scan split an assistant message into ordered parts of prose and typed A2UI messages — no hand-rolled JSON sniffing.

Scan reference →
╭╮

Real Lip Gloss rendering

Text variants, rounded Cards, Column / Row / List layout, Dividers and focusable Buttons — drawn with charm.land/lipgloss/v2.

Wire format →

The real A2UI protocol

Targets A2UI v0.9 via tmc/a2ui. a2tea invents no component types — it decodes and renders the actual A2UI catalog.

Read the spec notes →

Wired interaction

Tab / Shift+Tab cycle focusables; Enter emits event.ButtonClicked and a protocol-native ClientMessage back to the agent.

Events →

Embeddable by design

Render returns a render.Model child that never calls tea.Quit — the host owns the terminal, the layout, and the theme.

Composition →

Run it standalone

Wrap any surface in a2tea.Standalone to quit on q / Esc and forward terminal size — perfect for examples and manual testing.

See examples →
// THE FLOW

reply in, surface out

01

Agent emits A2UI

Messages arrive interleaved with prose, wrapped in <a2ui-json> tags.

02

Scan splits it

You get ordered []Part — each a chunk of Text plus typed Messages.

03

Render draws it

Messages composite into an embeddable render.Model the host draws.

The message lifecycle is applied in order — updateComponents composites by ID, updateDataModel resolves bindings, deleteSurface clears the surface.

// FOR HOSTS

a host is a handful of lines

Feed an assistant reply to Scan, render each part's text as prose, and hand each part's messages to Render. No quit handling — the model embeds into your program.

Full quickstart →
host.goGO
parts, err := a2tea.Scan(reply)
for _, p := range parts {
    if p.Text != "" {
        renderProse(p.Text)
    }
    model, err := a2tea.Render(p.Messages)
    if err != nil {
        continue // no renderable surface
    }
    draw(model) // an embeddable tea.Model
}