Labs/The generative UI spectrum
01

One intent, seven interfaces

The same request answered as text, markdown, a pre-built component, a declarative spec, sandboxed HTML, a whole app, and an agent driving an existing UI.

GoalFeel the trade-off between how much freedom the model gets and how much control, safety and consistency you keep.

static vs declarative vs open-endedcomponent catalogssandboxingbrand consistency
← more controlmore freedom →

Use ← → to move along the spectrum.

3 · What the model emitstool call
{
  "type": "tool-call",
  "toolName": "showExpenseApproval",
  "args": {
    "title": "Team offsite",
    "amount": 1240,
    "submitter": "Priya"
  }
}
3 · What the person seespre-built component
Expense
Team offsite
Submitted by Priya · 3 receipts
$1,240.00
within policy

tool call pending · waiting for the person

The model only chose showExpenseApproval and its three props. Layout, styling, copy on the buttons and what they do were written and tested by you.

Trade-offs at rung 30–5, editorial judgement
  • Control5how tightly you bound what can appear
  • Expressiveness2how many different interfaces the model can produce
  • Safety4how small the blast radius of a bad generation is
  • Brand fit5how likely the result matches your design system
  • Testability5how easily you can check it before a person sees it
  • Speed4how fast the first useful pixel appears
Reading this rung
Return path
The component's buttons resolve the tool call. The agent receives { approved: true } as the tool result.
Who ships it
Tool-call rendering in CopilotKit, the Vercel AI SDK and assistant-ui; most production agent UIs start here.
Takeaway
The model picks the component and fills its props. You wrote, styled and tested the component.
RungControlExpressivenessSafetyBrand fitTestabilitySpeed
515335
425335
524554
444444
252223
153231
312522
How it works: one intent, a policy per surfaceclick to expand

Pick the rung per surface, not per product. A payments confirmation belongs at rung 3. An exploratory analytics answer can live at rung 4 or 5. The same app usually mixes several.

Each rung moves the risk somewhere. Rungs 1–2 put it in parsing your words. Rungs 3–4 put it in your component library. Rung 5 puts it in a sandbox. Rung 7 puts it in what the agent clicks.

The return path matters as much as the render. Ask of any generative UI: when the person acts, what exactly reaches the agent, and who checked it?

// a "degrees of freedom" policy, decided per surface
const policy = {
  "expense.approve":  { rung: "static",      components: ["ExpenseApproval"] },
  "analytics.answer": { rung: "declarative", catalog: chartsAndTables },
  "report.explore":   { rung: "open",        sandbox: "allow-scripts",
                        bridge: ["ui/action"] },  // only these messages
}

function render(surface, modelOutput) {
  const p = policy[surface]
  if (p.rung === "static")      return <Registered name={modelOutput.toolName} {...modelOutput.args} />
  if (p.rung === "declarative") return <Catalog spec={modelOutput} allow={p.catalog} />
  if (p.rung === "open")        return <Sandbox html={modelOutput} allow={p.bridge} />
}