[FEATURE] Generative UI
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Server-Driven UI (SDUI) is an architectural pattern where the server defines UI structure at runtime instead of hardcoding it in the client. Companies like Airbnb, Netflix, and NuBank use SDUI to ship UI changes instantly without app store reviews. The server sends a JSON schema describing components, and the client renders them from a predefined catalog.
Generative UI extends this concept by replacing the traditional backend with an LLM. Instead of a rules engine deciding which components to show, the model chooses based on conversation context. There is a really nice paper from Google on this matter: https://generativeui.github.io/.
The key insight here: Generative UI = SDUI architecture + LLM as the decision engine, triggered via tool calls.
In very simple terms:
Traditional: User → Backend Rules → SDUI Schema → Client renders components
Generative: User → LLM Tool Call → UI Schema → Client renders components
Currently, Claude Code tool outputs are limited to text/markdown. The model can call tools, but cannot instruct the terminal to render rich, interactive components. This leaves a gap between Claude Code's capabilities and the Generative UI paradigm already available in web contexts.
Proposed Solution
Allow tools (including MCP servers) to return a ui field that Claude Code's renderer displays natively. Since Claude Code already uses React internally with a custom terminal renderer, this would be an extension of existing infrastructure.
The renderer would map component to a built-in catalog and render it inline. For interactive components, user selections would flow back to the model as tool results.
Component catalog approach (not arbitrary rendering):
- Predefined set of components with known props
- Tools select from catalog, don't generate arbitrary UI
- Consistent UX, predictable behavior, secure by design
Content Structure
UI elements are part of the content array, allowing text and UI to be intercalated:
{
content: [
{ type: "text", text: "Found 3 flights from GRU to JFK:" },
{ type: "ui", component: "table", id: "flights", columns: [...], rows: [...] },
{ type: "text", text: "UA 456 is the cheapest." }
]
}
Interaction Flow
When user interacts with a component, it flows back as a tool result:
{
type: "ui_interaction",
interactions: [
{ id: "flights", action: "select", value: { flight: "UA 456", ... } }
]
}
Component Examples
Table
{
type: "ui",
component: "table",
id: "flight-results",
columns: [
{ key: "flight", label: "Flight" },
{ key: "departure", label: "Departure" },
{ key: "arrival", label: "Arrival" },
{ key: "duration", label: "Duration" },
{ key: "price", label: "Price", align: "right" }
],
rows: [
{ flight: "AA 123", departure: "08:00", arrival: "14:30", duration: "9h30m", price: "$450" },
{ flight: "UA 456", departure: "10:30", arrival: "16:45", duration: "9h15m", price: "$380" },
{ flight: "DL 789", departure: "14:15", arrival: "20:30", duration: "9h15m", price: "$420" }
],
selectable: "single",
sortable: true
}
Renders as:
┌──────────┬───────────┬─────────┬──────────┬─────────┐
│ Flight │ Departure │ Arrival │ Duration │ Price │
├──────────┼───────────┼─────────┼──────────┼─────────┤
│ AA 123 │ 08:00 │ 14:30 │ 9h30m │ $450 │
│ UA 456 │ 10:30 │ 16:45 │ 9h15m │ $380 │ ◀
│ DL 789 │ 14:15 │ 20:30 │ 9h15m │ $420 │
└──────────┴───────────┴─────────┴──────────┴─────────┘
↑/↓ navigate Enter select s sort q cancel
Calendar
{
type: "ui",
component: "calendar",
id: "meeting-picker",
view: "week",
date: "2026-02-02",
calendars: [
{
name: "Alice",
color: "blue",
events: [
{ id: "1", title: "Standup", start: "2026-02-02T09:00", end: "2026-02-02T09:30" },
{ id: "2", title: "1:1 with Bob", start: "2026-02-02T14:00", end: "2026-02-02T15:00" }
]
},
{
name: "Bob",
color: "green",
events: [
{ id: "3", title: "Client call", start: "2026-02-02T10:00", end: "2026-02-02T11:30" }
]
}
],
selectable: true,
slotDuration: 30,
workingHours: { start: 9, end: 18 }
}
Renders as:
February 2026
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ Mon │ Tue │ Wed │ Thu │ Fri │
│ 2 │ 3 │ 4 │ 5 │ 6 │
────┼─────────┼─────────┼─────────┼─────────┼─────────┤
9h │░░░░░░░░░│ │ │░░░░░░░░░│ │
│░Standup░│ │ │░Alice░░░│ │
────┼─────────┼─────────┼─────────┼─────────┼─────────┤
10h │ │▓▓▓▓▓▓▓▓▓│ │ │ │
│ │▓Client▓▓│ │ │ │
────┼─────────┼─────────┼─────────┼─────────┼─────────┤
11h │ │▓▓▓▓▓▓▓▓▓│ │ │ │
│ │▓call▓▓▓▓│ │ │ │
────┼─────────┼─────────┼─────────┼─────────┼─────────┤
12h │ │ │ │ │ │
────┼─────────┼─────────┼─────────┼─────────┼─────────┤
13h │ │ │████████▶│ │ │
│ │ │ Selected│ │ │
────┼─────────┼─────────┼─────────┼─────────┼─────────┤
14h │░░░░░░░░░│ │ │ │ │
│░1:1 Bob░│ │ │ │ │
────┴─────────┴─────────┴─────────┴─────────┴─────────┘
░ Alice ▓ Bob █ Selected slot
←/→ navigate Enter select q cancel
Kanban
{
type: "ui",
component: "kanban",
id: "sprint-board",
columns: [
{
id: "todo",
title: "To Do",
cards: [
{ id: "1", title: "Fix auth bug", labels: ["bug", "urgent"], assignee: "alice" },
{ id: "2", title: "Add dark mode", labels: ["feature"] }
]
},
{
id: "in-progress",
title: "In Progress",
limit: 3,
cards: [
{ id: "3", title: "Refactor API client", labels: ["tech-debt"], assignee: "bob", progress: 60 }
]
},
{
id: "done",
title: "Done",
cards: []
}
],
draggable: true,
cardActions: [
{ id: "view", label: "View details" },
{ id: "assign", label: "Assign to me" }
]
}
Renders as:
┌─ To Do (2) ──────────┬─ In Progress (1/3) ──┬─ Done (0) ───────────┐
│ │ │ │
│ ┌──────────────────┐ │ ┌──────────────────┐ │ │
│ │ Fix auth bug │ │ │ Refactor API │ │ │
│ │ 🏷 bug urgent │ │ │ 🏷 tech-debt │ │ │
│ │ 👤 alice │ │ │ 👤 bob │ │ │
│ └──────────────────┘ │ │ ████████░░ 60% │ │ │
│ │ └──────────────────┘ │ │
│ ┌──────────────────┐ │ │ │
│ │ Add dark mode │ │ │ │
│ │ 🏷 feature │ │ │ │
│ │ │ │ │ │
│ └──────────────────┘ │ │ │
│ │ │ │
│ │ │ │
└──────────────────────┴──────────────────────┴──────────────────────┘
←/→ columns ↑/↓ cards Enter view m move a assign to me
Logs
{
type: "ui",
component: "logs",
id: "deploy-logs",
source: "deployment-xyz",
streaming: true,
lines: [
{ ts: "2026-02-02T10:00:01Z", level: "info", service: "api", message: "Starting deployment..." },
{ ts: "2026-02-02T10:00:02Z", level: "info", service: "api", message: "Pulling image ghcr.io/app:v2.3.1" },
{ ts: "2026-02-02T10:00:05Z", level: "warn", service: "api", message: "Slow network detected, retrying..." },
{ ts: "2026-02-02T10:00:08Z", level: "info", service: "db", message: "Running migrations..." },
{ ts: "2026-02-02T10:00:12Z", level: "error", service: "db", message: "Migration failed: column already exists" }
],
filters: {
level: ["info", "warn", "error"],
service: ["api", "db", "worker"]
},
searchable: true,
follow: true
}
Renders as:
Deploy: deployment-xyz ● STREAMING
─────────────────────────────────────────────────────────────────
Filter: [✓] info [✓] warn [✓] error Service: [all ▾] 🔍 ___
10:00:01 INFO api Starting deployment...
10:00:02 INFO api Pulling image ghcr.io/app:v2.3.1
10:00:05 WARN api Slow network detected, retrying...
10:00:08 INFO db Running migrations...
10:00:12 ERROR db Migration failed: column already exists
│
└─→ ERROR DETAILS ─────────────────────────────────────
relation "users" already has column "email_verified"
at migration 20260201_add_verification.sql:12
───────────────────────────────────────────────────
─────────────────────────────────────────────────────────────────
f filter / search F follow (on) ↑/↓ scroll q close
Component Catalog
| Component | Purpose | Interaction |
|-----------|---------|-------------|
| table | Tabular data with sorting | select row(s) |
| calendar | Schedule view, time picking | select slot |
| kanban | Board with cards | move, select |
| logs | Real-time log streaming | filter, search |
Alternative Solutions
claude-canvas is a proof-of-concept that demonstrates Generative UI is possible in Claude Code today. It uses tmux to spawn a separate terminal pane and renders TUI components with Ink. However, it requires users to run Claude Code inside tmux, adds external process management complexity, and remains an unsupported PoC. The approach validates the concept but isn't a sustainable solution.
MCP Apps is the official extension for rich UI in the MCP ecosystem. It allows tools to return full HTML/JavaScript interfaces rendered in sandboxed iframes. The problem: it only works in Claude Desktop and Claude Web. The CLI has no iframe runtime, so MCP Apps simply don't render in Claude Code. This creates a capability gap between web and terminal users.
Hooks + external renderer is a workaround where post-tool-use hooks launch external applications (browsers, native apps) to display rich UI. This fragments the user experience—context switching between terminal and external windows breaks flow. It also requires users to configure hooks and install additional dependencies, raising the barrier to entry.
Priority
Low - Nice to have
Feature Category
Interactive mode (TUI)
Use Case Example
Claude Code already renders custom UI for some tools. TaskList shows interactive task lists. AskUserQuestion renders styled buttons. However, these components are constrained to specific screen regions (specifically the bottom bar), and it's unclear how reusable the underlying mechanism is.
A unified, exposed component catalog would let MCP developers build entire apps inside Claude Code itself. A calendar server returns { component: "calendar", ... } and gets interactive scheduling. A CI/CD server streams { component: "logs", ... } with real-time filtering. A project tracker renders as a kanban board. Developers declare what to show; Claude Code handles how to render it.
Additional Context
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗