React bindings for OpenUI Lang. Use this package when your model needs to emit structured UI and your React app needs to render it while the response is still streaming.
Links: Package docs | OpenUI Lang guide | GitHub repo
npm install @openuidev/react-lang
# or
pnpm add @openuidev/react-langPeer dependencies: react >=19.0.0
Without slot children, Renderer keeps query defaults or cached results visible while loading and after failures. A spinner is shown while queries load, and onError receives structured errors. Provide queryLoader to customize the indicator, or slot children for custom layouts. No error/retry view is shown by default.
Use slots to customize loading and failure views:
<Renderer.Root response={response} library={library} toolProvider={toolProvider}>
<div style={{ position: "relative" }}>
<Renderer.Content />
<Renderer.QueryLoading>Loading data…</Renderer.QueryLoading>
<Renderer.QueryError>
<div role="alert">
<p>We couldn't load your data. Please try again.</p>
<Renderer.Retry>Try again</Renderer.Retry>
</div>
</Renderer.QueryError>
</div>
</Renderer.Root>Renderer.Root owns one runtime and renders only its children. Slots can sit inside wrappers or portals under that root. Renderer.Content keeps generated components mounted while queries load or fail; failed results remain hidden until recovery. Omitting a slot omits its presentation. Plain Renderer supplies content and a loading indicator without hiding results on failure.
Renderer.QueryLoading shows its children while queries await generation or fetch data, unless failures are being displayed. Omit children to use queryLoader, falling back to the default spinner. Position custom overlays within your own relative container. Renderer.QueryError shows its children on failure and renders nothing without children. Add Renderer.Retry explicitly if you want a retry button. Renderer.Retry disables itself during loading and when no queries have failed. It accepts standard button props; its click handler can prevent the retry with event.preventDefault().
Use useRendererQuery() inside the root to read { isLoading, errors, retry, isRetrying } in your own components. Generation progress remains available through useIsStreaming(). Do not create another root for each slot.
Renderer accepts slot children to replace its default content composition.
While queries load, Renderer.Content renders their defaults or previously successful data. During streaming edits, previously loaded results remain visible with loading feedback until the updated response is ready.
@openuidev/react-lang is the React runtime layer for OpenUI Lang. It covers the loop most apps need:
- Define components that a model is allowed to use, with Zod schemas for props.
- Generate prompts from that component library so the model knows the exact output language.
- Render streamed output with
<Renderer>as OpenUI Lang arrives from your backend.
import { defineComponent } from "@openuidev/react-lang";
import { z } from "zod";
const Greeting = defineComponent({
name: "Greeting",
description: "Displays a greeting message",
props: z.object({
name: z.string().describe("The person's name"),
mood: z.enum(["happy", "excited"]).optional().describe("Tone of the greeting"),
}),
component: ({ name, mood }) => (
<div className={mood === "excited" ? "text-xl font-bold" : ""}>
Hello, {name}!
</div>
),
});import { createLibrary } from "@openuidev/react-lang";
const library = createLibrary({
components: [Greeting, Card, Table /* ... */],
root: "Card", // optional default root component
});const systemPrompt = library.prompt({
preamble: "You are a helpful assistant.",
additionalRules: ["Always greet the user by name."],
examples: ["<Greeting name='Alice' mood='happy' />"],
});import { Renderer } from "@openuidev/react-lang";
function AssistantMessage({ response, isStreaming }) {
return (
<Renderer
response={response}
library={library}
isStreaming={isStreaming}
onAction={(event) => console.log("Action:", event)}
/>
);
}| Export | Description |
|---|---|
defineComponent(config) |
Define a single component with a name, Zod props schema, description, and React renderer |
createLibrary(definition) |
Create a library from an array of defined components |
| Export | Description |
|---|---|
Renderer |
Render OpenUI Lang |
RendererProps:
| Prop | Type | Description |
|---|---|---|
response |
string | null |
Raw OpenUI Lang text from the model |
library |
Library |
Component library from createLibrary() |
isStreaming |
boolean |
Whether the model is still streaming (disables form interactions) |
onAction |
(event: ActionEvent) => void |
Callback when a component triggers an action |
onStateUpdate |
(state: Record<string, any>) => void |
Callback when form field values change |
initialState |
Record<string, any> |
Initial form state for hydration |
queryLoader |
React.ReactNode |
Custom loading indicator; defaults to a spinner |
onParseResult |
(result: ParseResult | null) => void |
Callback when the parse result changes |
| Export | Description |
|---|---|
createParser(library) |
Create a one-shot parser for complete OpenUI Lang text |
createStreamingParser(library) |
Create an incremental parser for streaming input |
The streaming parser exposes two methods:
| Method | Description |
|---|---|
push(chunk) |
Feed the next chunk; returns the latest ParseResult |
getResult() |
Get the latest result without consuming new data |
After the stream ends, check meta.unresolved for any identifiers that were referenced but never defined. During streaming these are expected (forward refs) and are not treated as errors.
ParseResult.meta.errors contains structured OpenUIError objects. Each error has a type discriminant (currently always "validation") and a code for consumer-side filtering:
| Code | Meaning |
|---|---|
missing-required |
Required prop absent with no default |
null-required |
Required prop explicitly null with no default |
unknown-component |
Component name not found in the library schema |
excess-args |
More positional args passed than the schema defines |
Errors do not affect rendering. The parser stays permissive and renders what it can. Use code to decide how to surface or log errors:
const result = parser.parse(output);
const critical = result.meta.errors.filter(
(e) => e.code === "unknown-component"
);To check for unresolved references after streaming, inspect meta.unresolved:
if (result.meta.unresolved.length > 0) {
console.warn("Unresolved refs:", result.meta.unresolved);
}Use these inside component renderers to interact with the rendering context:
| Hook | Description |
|---|---|
useIsStreaming() |
Whether the model is still streaming |
useRenderNode() |
Render child element nodes |
useTriggerAction() |
Trigger an action event |
useGetFieldValue() |
Get a form field's current value |
useSetFieldValue() |
Set a form field's value |
useSetDefaultValue() |
Set a field's default value |
useFormName() |
Get the current form's name |
| Export | Description |
|---|---|
useFormValidation() |
Access form validation state |
useCreateFormValidation() |
Create a form validation context |
validate(value, rules) |
Run validation rules against a value |
builtInValidators |
Built-in validators (required, email, min, max, etc.) |
import type {
Library,
LibraryDefinition,
DefinedComponent,
ComponentRenderer,
ComponentRenderProps,
ComponentGroup,
PromptOptions,
RendererProps,
ActionEvent,
ElementNode,
ParseResult,
OpenUIError,
ValidationErrorCode,
LibraryJSONSchema,
} from "@openuidev/react-lang";Libraries can also produce a JSON Schema representation of their components:
const schema = library.toJSONSchema();
// schema.$defs["Card"] → { properties: {...}, required: [...] }
// schema.$defs["Greeting"] → { properties: {...}, required: [...] }