Renderer turns into a real React UI.
This integration is well-suited for data-rich outputs like reports, dashboards, and data explorers, where the model is both the data analyst and the UI designer.
How it works
- Generate the system prompt: call
openuiLibrary.prompt()once at startup; it produces a complete openui-lang reference that the model uses to write valid component trees - Inject on first message: send the system prompt as the opening system message when a new conversation starts
- Model writes openui-lang: the model responds with a program like
root = Stack([header, kpis, chart])instead of prose - Render with
Renderer: pass the text to OpenUI’sRendererand the component library; it parses and renders the tree
Installation
Import the component styles
Import OpenUI’s bundled styles in your CSS entry point or directly in your root component:Generate the system prompt
OpenUI ships aopenuiLibrary.prompt() function that generates the complete openui-lang reference, with all component signatures, syntax rules, streaming tips, and examples. Call it once at module load time:
preamble overrides the default persona. Add additionalRules to inject task-specific constraints:
Inject the system prompt via useStream
Send the system prompt as the first message of every new thread. Checkstream.messages.length === 0 to detect a fresh thread and prepend a system message:
Render with the Renderer
Pass the AI message’s text content directly toRenderer along with openuiLibrary:
isStreaming={true} during the active stream so the Renderer handles unresolved references gracefully as definitions arrive.
The openui-lang format
The model writes a program rather than a JSON spec. Every statement is an assignment;root is the entry point. The official prompt teaches the model this format, including hoisting — writing root first so the UI shell appears immediately:
root line is written first so the page structure appears immediately and each section fills in as the model defines it.
Progressive rendering utilities
WiringuseStream to Renderer directly causes results in re-rendering on every streaming token and produces hundreds of no-op re-parses per response. This causes chart components to crash when their data hasn’t arrived yet. The utilities below solve these problems:
Copy the full block into your project and pass
stable to <Renderer>:
Follow-up queries
OpenUI’sButton component supports a continue_conversation action type. When the user clicks a follow-up button, Renderer fires onAction and the AIMessageView above submits the button’s label as the next user message, exactly the same code path as typing in the input.
Add an “Explore Further” section to every report via additionalRules in the system prompt:
Build a parallel dashboard with Deep Agents
The flow above renders one OpenUI program into one surface. For richer apps, a Deep Agents coordinator can delegate to several specialist agents that each stream their own OpenUI panel concurrently, all over oneuseStream connection. The OpenUI parallel dashboard example turns one dashboard brief into independently streaming Stripe, PostHog, GitHub, and Calendar panels, with no custom graph or stream-demultiplexing code.
Share one OpenUI library
Use the same library object on the server (to generate the panel prompt) and on the client (as theRenderer prop) so the components the model is told about always match the ones the renderer can draw:
library.ts
Define the coordinator and panel agents
createDeepAgent builds a coordinator whose only job is routing: it picks the specialists a brief needs and emits all of their task() calls in one message so the panels run concurrently. Each panel subagent shares one pre-generated OpenUI system prompt and receives only the tools for its data domain.
agent.ts
root so its renderer can paint before the model finishes the remaining statements.
Register the graph
Pointlanggraph.json at the exported coordinator:
langgraph.json
Discover and render panels on the frontend
OneuseStream connection carries the coordinator and every panel. The panels are not hardcoded: each parallel task() call surfaces as a stream.subagents snapshot. For each snapshot, scope a useMessages(stream, snapshot) projection so a panel receives only its own subagent’s messages, then feed its OpenUI program into an isolated Renderer:
App.tsx
Panel is memoized on its snapshot identity, tokens from one panel never re-render another.
Best practices
- Generate the system prompt at module load: not inside a React component; the prompt is several kilobytes and should be computed once
- Inject the system prompt only on fresh threads: check
stream.messages.length === 0and skip injection on subsequent turns to avoid duplicating the prompt in the thread history - Use hoisting order: write
root = Stack([...])first; the UI shell appears immediately and sections fill in progressively as the model defines each one - Gate on complete statements: avoid re-rendering the Renderer on every token; update only when a full statement (
name = ComponentCall(...)) has arrived - Verify chart data before rendering: chart components need their
Seriesand label arrays defined before they’re included in the stable snapshot - Keep camelCase variable names: the openui-lang parser only accepts camelCase identifiers; reinforce this in the system prompt’s
additionalRules - Delegate panels in one message: when fanning out to Deep Agents specialists, emit all
task()calls in a single coordinator message so the panels stream concurrently rather than one at a time - Scope each panel to its subagent: discover panels from
stream.subagentsand pass each snapshot touseMessages(stream, snapshot)so a panel renders only its own subagent’s output
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

