Skip to content
Language Feature

Generative UI

Agents that render rich, interactive components inline — tables, charts, forms, diffs — not just text. No frontend code required.

Built-in
11 Components
Custom Extensible

Beyond Text

Traditional chatbot UIs render everything as text bubbles. But agents produce structured data — query results, validation reports, deployment status, comparison diffs. Rendering all of that as markdown is a poor experience.

Haira's Generative UI lets tools declaratively control how their output appears. A database query renders as a table. A validation result renders as a status card. A deployment pipeline renders as a progress tracker. All inline in the chat, all without writing frontend code.

How It Works

Tools return UI components using the ui module. The runtime does two things with every result:

For the User
Rich Component
Streamed to the frontend via the ARP protocol and rendered as an interactive table, card, chart, or form.
For the Agent
Text Summary
A compact text representation is sent to the LLM so it can reason about the data and decide next steps.

Neither path is sacrificed. The user sees a beautiful table, the agent sees structured data.

Example — Tool with UI Output
haira
import "ui"

tool query_database(query: string) -> any {
    """Executes a SQL query and displays results."""
    rows, err = postgres.query(db, query)
    if err != nil {
        return ui.status_card("error", "Query Failed", conv.to_string(err))
    }
    return ui.table("Query Results", headers, rows)
}

Enable UI on an agent with a single line:

haira
agent DataExplorer {
    provider: OpenAI
    tools: [query_database, search_data]
    ui: ui
    memory: conversation(max_turns: 30)
}

11 Built-in Components

status-card
Success, error, warning result cards with collapsible sections
table
Searchable, scrollable data tables with tabs and sticky headers
code-block
Syntax-highlighted code with copy button and multiple tabs
diff
Side-by-side before/after comparison with syntax highlighting
key-value
Property lists with styled values for metadata display
progress
Multi-step pipeline tracker with status per step
chart
Line, bar, pie, area, and scatter data visualizations
form
Interactive forms with text, select, checkbox, and textarea fields
confirm
Yes/no confirmation dialogs for destructive actions
choices
Button or list option pickers for user selection
product-cards
Image card grids for e-commerce and catalog displays
Usage
haira
import "ui"

ui.status_card("success", "Deploy Complete", "All 3 services updated")
ui.table("Results", ["Name", "Email"], [["Alice", "a@co"], ["Bob", "b@co"]])
ui.key_value("Server Info", {"Region": "us-east-1", "Status": "healthy"})
ui.chart("bar", "Revenue", ["Q1", "Q2", "Q3", "Q4"], [dataset])
ui.confirm("Delete this record?", "Yes, delete", "Cancel")
ui.group(
    ui.status_card("success", "Query Complete", "42 rows"),
    ui.table("Results", headers, rows)
)

Auto-generated Web UI

Every Haira workflow automatically gets a web UI. Define a workflow, Haira generates the form:

haira
@webui(title: "File Summarizer", description: "Upload a file and get an AI summary")
@post("/summarize")
workflow Summarize(document: file, context: string) -> { summary: string } {
    content, err = io.read_file(document)
    if err != nil { return { summary: "Failed to read file." } }
    reply, err = Summarizer.ask("Summarize: ${content}")
    if err != nil { return { summary: "AI error." } }
    return { summary: reply }
}

The @webui decorator sets the title and description. file parameters render as upload inputs. Streaming workflows (-> stream) get a full chat interface.

Streaming Chat UI

Streaming workflows get the richest experience — real-time token streaming, tool execution cards, and inline UI components:

haira
@webhook("/chat")
workflow Chat(message: string, session_id: string) -> stream {
    return Assistant.stream(message, session: session_id)
}

fn main() {
    http.Server([Chat]).listen(8080)
}

The chat UI communicates via the ARP protocol — handling text deltas, tool lifecycle events, and rich component rendering over WebSocket or SSE.

Custom Components

For domain-specific needs, drop TypeScript Web Components into a components/ directory:

components/gantt-chart.ts
typescript
export class HairaGanttChart extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: "open" });
  }
  setProps(props) {
    // Render your custom UI
  }
}

export default {
  tag: "haira-gantt-chart",
  component: HairaGanttChart,
};

Custom components inherit Haira's theme via CSS custom properties and can dispatch haira-action events that become chat messages. The compiler discovers, bundles, and embeds them at build time.

The Pipeline

1
Tools
Return typed data via ui.* functions
2
Runtime
Emits ARP messages over WebSocket or SSE
3
Frontend
Renders matching component inline in chat

No separate frontend repo. No API client to maintain. No component library to install. One .haira file, one binary, full UI.

Released under the Apache-2.0 License.