/

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.

Read the docs

show_widgettitle: sunlight_travel_time

fig. 01 · a recorded show_widget call, replayed into a live frame0 of 3,248 characters

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
fig. 02 · your page, the port, and the runtime in its own frame

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()

Pick a starting point