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:
| Problem | Solution |
|---|---|
| Partial string literals | truncateAtOpenString / closeOrTruncateOpenString — drop or close incomplete strings before parsing |
| Mid-token churn | useStableText — gate Renderer updates on complete statement boundaries (name = Expr(…)) rather than every token |
| Chart null-data crashes | chartDataRefsResolved — verify a chart’s Series and label arrays are defined before including it in the snapshot |
No root yet / fallback | buildProgressiveRoot — synthesise a root = Stack([…]) from top-level variables when the model hasn’t written one |
| Snake_case identifiers | sanitizeIdentifiers — the parser only accepts camelCase; convert any snake_case names the model emits |
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:
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
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

