Widgets that stream from the model
Render model-written HTML and SVG while it streams, each widget in a sandboxed frame on its own domain, in your app’s theme.
show_widgettitle: sunlight_travel_time
Two ways to draw a widget
How a widget streams
Each widget is a Safe Content Frame that loads a small runtime once. Code then streams to the runtime over a private MessagePort.
Your page
https://your.app
- createWidget()
- widget.write(chunk)
- widget.end()
- onPrompt, onError
MessagePort, JSON-RPC
- genframe/write
- genframe/end
- genframe/screenshot
- ui/message
- tools/call
- genframe/error
<hash>.scf.auiusercontent.com
Runtime
Parses the partial markup on each animation frame and morphs it into the page. Scripts wait for the end.
- kept
- patched
- new, fades in
- held script
In the package
Streams as it’s written
Partial markup renders as it arrives. Scripts run once the code is complete.
write(), end()
Your theme
Your page’s CSS variables become tokens in the frame, and follow theme changes.
readThemeTokens(), setTheme()
MCP Apps compatible
The frame speaks the MCP Apps ui/* protocol, so MCP Apps widgets render too.
ui/initialize, ui/message
Screenshots and repair
Errors, console output, blank renders, and a PNG go back to the model to fix.
previewWidget(), repairLoop()
Spec mode
JSON patches against a catalog of your components, validated as they stream.
defineCatalog(), <SpecRenderer>
Storage that persists
Give a widget an id and its localStorage survives reloads. Clear it from the host.
id, clearWidgetStorage()
assistant-ui toolkit
Widget tool calls render in the thread while their arguments stream.
createWidgetToolkit()
Works without assistant-ui
The core is plain JavaScript. The React and assistant-ui entries are optional.
createWidget()