Agent-to-UI rendering engine — transforms agent text output into rich visual interfaces across terminals, web, and mobile surfaces.
Part of the OpenConstruct terrain/a2ui ecosystem.
When AI agents produce text, it's often structured data masquerading as prose. a2ui-render heuristically parses that text into a UIDL (Universal Interface Description Language), then renders it to the right surface — ANSI terminal, styled HTML, clean Markdown, or voice scripts.
Agent Text → A2UI::parse() → Uidl → A2UI::render(surface, theme) → RenderedOutput
↓
HTML / ANSI / Markdown / Voice
use a2ui_render::{A2UI, RenderSurface, Theme, ThemePreset};
let a2ui = A2UI::new();
// Parse agent text into a UIDL
let uidl = a2ui.parse("Hello world\n- Apples\n- Bananas");
// Render with a dark terminal theme
let theme = Theme::from_preset(ThemePreset::DarkTerminal);
let output = a2ui.render(&uidl, RenderSurface::Terminal, &theme);
println!("{}", output.ansi.unwrap());Each component can be created via Component::from_text(description) and rendered with component.render(target).
| Component | Text Syntax | Description |
|---|---|---|
| TextBlock | Hello world |
Plain text content (fallback) |
| Button | Click here [button] or [button: Submit] |
Clickable button |
| ActionButton | action: Deploy to production |
Emphasized action button |
| List | - item1\n- item2 or 1. First\n2. Second |
Bulleted or numbered list |
| Table | table: Name, Age | Alice, 30 | Bob, 25 |
Data table |
| Card | card: Title | Body content |
Container with title and children |
| Code | ```rust\nfn main() {}\n``` |
Syntax-highlighted code block |
| Chart | chart: bar apples=10 bananas=15 |
ASCII bar chart |
| ProgressBar | progress: 75% or progress 3/5 |
Visual progress indicator |
| StatusBadge | [status:ok:All Systems Go] |
Colored status indicator |
use a2ui_render::{Component, OutputTarget};
// Create from natural text
let comp = Component::from_text("progress: 60%");
// Render to any target
let html = comp.render(OutputTarget::Html);
let ansi = comp.render(OutputTarget::Ansi);
let markdown = comp.render(OutputTarget::Markdown);
// Or use themed rendering
let theme = Theme::from_preset(ThemePreset::DarkWeb);
let styled_html = comp.to_html(&theme);
let styled_ansi = comp.to_ansi(&theme);Six built-in presets covering dark/light modes for terminal, web, and mobile:
| Preset | Description |
|---|---|
DarkTerminal |
Dark background, bright text, cyan accents |
LightTerminal |
Light background, dark text, blue accents |
DarkWeb |
GitHub-dark inspired, green buttons |
LightWeb |
GitHub-light inspired, green buttons |
MobileDark |
iOS dark mode, blue accent |
MobileLight |
iOS light mode, blue accent |
use a2ui_render::{Theme, ThemePreset};
let theme = Theme::from_preset(ThemePreset::DarkWeb);
// Access CSS colors, ANSI codes, etc.
println!("{}", theme.foreground.unwrap()); // "#e0e0e0"
println!("{}", theme.ansi_foreground()); // "\x1b[38;5;252m"Templates provide structured layouts for common agent output patterns:
use a2ui_render::{A2UI, TemplateRegistry, RenderSurface, Theme, ThemePreset};
let reg = TemplateRegistry::new();
let a2ui = A2UI::new();
// Apply a built-in template
let text = "System Status\n\n[status:ok:All Good]\n\nprogress: 80%\n\nDetails here";
let uidl = reg.apply("status_dashboard", text, &a2ui).unwrap();
// Render
let theme = Theme::from_preset(ThemePreset::DarkTerminal);
let output = a2ui.render(&uidl, RenderSurface::Terminal, &theme);status_dashboard— Grid layout: title, status badges, progress bars, detail cardsreport— Vertical layout: header, data table, chartaction_panel— Vertical layout: info card, action buttons, status badge
use a2ui_render::{Template, TemplateSection, Layout, ComponentVariant, ThemePreset, Style};
let custom = Template {
name: "my_template".into(),
layout: Layout::Grid { columns: 2 },
theme: ThemePreset::DarkWeb,
sections: vec![
TemplateSection {
name: "header".into(),
component_hint: ComponentVariant::TextBlock,
style: Some(Style { bold: Some(true), ..Default::default() }),
},
TemplateSection {
name: "data".into(),
component_hint: ComponentVariant::Table,
style: None,
},
],
};
let mut reg = TemplateRegistry::new();
reg.register(custom);// Terminal (ANSI output with colors)
let output = a2ui.render(&uidl, RenderSurface::Terminal, &theme);
println!("{}", output.ansi.unwrap());
// Web (styled HTML)
let output = a2ui.render(&uidl, RenderSurface::Web, &theme);
// output.html — themed HTML with inline styles
// output.markdown — clean Markdown representation
// Mobile (HTML + Markdown + JSON)
let output = a2ui.render(&uidl, RenderSurface::Mobile, &theme);
// Voice (natural language script)
let output = a2ui.render(&uidl, RenderSurface::Voice, &theme);
println!("{}", output.voice_script.unwrap());Quick visual preview of a UIDL's layout:
let preview = a2ui.preview(&uidl);
// ╔══════════════════════════════════════════════════╗
// ║ Agent Output ║
// ╠══════════════════════════════════════════════════╣
// ║ [0] Text: Hello world ║
// ║ [1] List (2 items) ║
// ╚══════════════════════════════════════════════════╝use a2ui_render::{Interaction, RenderSurface};
let interaction = Interaction {
handler_name: "on_button_click".into(),
component_id: Some("btn-0".into()),
value: serde_json::json!({ "clicked": true }),
};
let callback = a2ui.handle_interaction(&rendered, interaction);
// callback.handler_name → "on_button_click"
// callback.payload → "{\"clicked\":true}"a2ui-render is the rendering layer of the OpenConstruct terrain/a2ui system:
- Terrain — Orchestrates agent interactions with environments
- A2UI — Defines the agent-to-UI protocol (UIDL format)
- a2ui-render (this crate) — Renders UIDL to visual surfaces
- Consumers — Terminal apps, web dashboards, mobile apps, voice assistants
The UIDL format is JSON-based and language-agnostic. Any agent that can produce text can be routed through a2ui-render to produce rich interfaces.
MIT