OpenUI
OpenUI is a Generative UI framework whose model-facing format, OpenUI Lang, is a compact, line-oriented language designed for streaming. Instead of generating executable UI code, an Agent emits component expressions such as Card(...) and Button(...) from a library that the application controls.
@lynx-js/genui/openui is the OpenUI Lang renderer for ReactLynx. It incrementally parses model output, maps recognized expressions to registered ReactLynx components, and renders the result through Lynx's native rendering pipeline.
The application still owns the component implementations, visual design, available tools, navigation, and Agent connection. The OpenUI Library defines the component vocabulary; toolProvider and onAction separately gate tools and host-side effects.
Live example
The example below streams a mock OpenUI Lang response into the Renderer. As chunks arrive, the Renderer progressively builds a two-day Shanghai itinerary. Select Ask for alternatives or Use this plan to see the host-facing Action, or select Replay to restart the stream.
How OpenUI works
An OpenUI integration has five parts:
- Define a Library: Create component definitions and their Zod schemas.
createOpenUiLibrary()starts with layout, content, button, media, overlay, and input components, then applies your extensions. - Instruct the Agent: Give the model an OpenUI system prompt generated from the same component contract.
- Stream OpenUI Lang: Append each model delta to the response text. Statements use functional notation, for example
title = TextContent("Hello"). - Parse and render:
<OpenUiRenderer>incrementally parses completed statements, resolves forward references, and renders registered ReactLynx components. - Handle runtime effects: The Renderer manages local state and tool calls, while host-facing actions are delivered to your application through
onAction.
This keeps generated components inside an explicit vocabulary: unknown component names are not rendered, and the Agent never supplies arbitrary ReactLynx code. Language built-ins such as Query() and Mutation() are runtime capabilities, so enforce their actual permissions through toolProvider and onAction.
Quick start
Install the renderer and its ReactLynx peers:
Import the OpenUI styles in your application stylesheet:
The published @lynx-js/genui@0.0.6 package uses renderer.css. The current lynx-stack/main source has moved the host-facing theme variables to @lynx-js/genui/openui/styles/theme.css; use that path when developing against main or a release that exports it.
Create one stable Library and pass OpenUI Lang source to the Renderer:
OpenUI Lang arguments are positional and follow the component's schema order. For example, the built-in Stack signature is Stack(children, direction?, wrap?, gap?, align?, justify?). Use the Components Playground to inspect current signatures instead of guessing argument positions.
Generate Agent instructions
The Agent must know the same component and language contract as the Renderer. Generate the built-in system prompt on the server, where ReactLynx runtime dependencies are not available:
Pass systemPrompt as the model's system instruction. When you add custom components, keep the server-side prompt definitions and the client-side Library definitions synchronized so the Agent does not emit components or arguments that the Renderer does not recognize.
Stream model output
response is the complete text received so far, not the latest delta. Append chunks in order and keep isStreaming aligned with the real network lifecycle:
While isStreaming is true, the runtime delays Query() and Mutation() work and suppresses transient parse errors. Set it to false only after the stream finishes; otherwise incomplete tool calls may run too early, or queries may never start.
Add state, tools, and actions
OpenUI Lang v0.5 can describe reactive state, reads, writes, and ordered actions:
Connect Query() and Mutation() names to application capabilities through toolProvider:
Query() runs after streaming finishes and runs again when a referenced $variable changes; its default value keeps the UI renderable before data arrives. Mutation() remains idle until an Action invokes it with @Run. Action steps are visited in order, but only @Run(mutation) is awaited and stops the remaining steps on failure. @Run(query) starts a refetch and immediately advances to the next step.
onStateUpdate snapshots store $variables as direct values, while form controls can use { value, componentType } entries. Make persistence code tolerant of repeated snapshots and these two shapes.
The runtime and host divide Action steps as follows:
toolProvider can also be an MCP client with a compatible callTool({ name, arguments }) method. Treat every tool call as untrusted input: validate arguments, enforce authorization on the backend, and expose only the capabilities the generated interface needs.
Extend the component Library
Use defineComponent to pair a ReactLynx renderer with a Zod schema, then append it to the default Library:
Declare zod as a direct application dependency when defining components. The schema establishes the component's argument order and metadata used for prompt and JSON Schema generation. The current parser checks structural constraints such as component names, required arguments, and extra arguments, but it does not perform a complete zod.parse() of every value; validate security-sensitive values inside components and tools. The component callback receives { props, renderNode, statementId }; it does not receive the component props as its top-level argument.
createOpenUiLibrary() appends custom definitions to its built-ins; a custom component with the same name overrides that name, but the public factory does not remove the other defaults to form a strict allowlist. You can inspect the resulting contract with library.toJSONSchema(). Keep this contract stable for the lifetime of an active stream by memoizing the Library instead of creating it during every render.
Try the Playground
The Lynx GenUI Playground provides three OpenUI workflows:
- Create: Describe a surface, inspect the streamed OpenUI Lang, and render it in Lynx Preview.
- Examples: Edit and replay examples for layouts, reactive state,
Query(), andMutation(). - Components: Browse the built-in Library, positional prop contracts, usage snippets, and live previews.
For individual symbols and props, see the @lynx-js/genui/openui API reference. The implementation is developed in the Lynx Stack OpenUI source.