Use when reasoning about the pattern where a language model emits structured output describing UI components or a UI sub-tree that an application renders for the user. Covers the typed-schema component palette, JSON Schema/function-calling constraints, two render substrates (typed component tree vs sandboxed iframe), the app-side render pipeline, bidirectional interaction loop via postMessage/JSON-RPC, the security boundary between model author and application renderer, and distinctions from chat markdown, prebuilt-widget routing, RSC streaming, and model-emits-code patterns. Do NOT use for page-level rendering taxonomy (use rendering-models), the tool-call protocol cycle (use tool-call-flow), untrusted-content defenses (use prompt-injection-defense), or general component-library architecture (use design-system-architecture). Do NOT use for design the JSON shape of an HTTP API endpoint (use api-design).
Generative UI is the pattern in which a language model emits — as structured output constrained by a typed schema — a specification of a UI component or sub-tree that an application then renders for the user. The load-bearing distinction is that the model's output is neither a chat response, nor a tool call asking for execution, nor raw markup it freely authors: it is a typed instance of a component-vocabulary schema, plus the data to populate it. Three contracts must hold simultaneously for the pattern to work — the component schema (the typed vocabulary the model and application share), the generation constraint (the mechanism that forces the model's emission to be a valid schema instance — provider-native structured output or strict tool calling, not best-effort function calls), and the render pipeline (the application code that turns a validated spec into pixels). Rendering happens through one of two legitimate substrates that differ in where safety comes from: a typed-component-tree where safety is by restriction (the model can only name components in the published vocabulary; no HTML executes), or a sandboxed-iframe where safety is by isolation plus host mediation (a developer-authored HTML resource runs in a sandbox that cannot reach the parent DOM, fed the model's data over a postMessage/JSON-RPC bridge). The invariant across both is that the model never authors raw HTML injected into the host DOM — it authors a typed selection and data. The application owns rendering, the interaction layer, and visual design; the model owns the choice of which components to compose and with what data. A bidirectional interaction loop closes the cycle: a user action on a rendered component becomes a typed event the application encodes as the next turn the model reasons about, with destructive effects gated as separate, user-identity-authorized tool calls rather than auto-executed from a click.
Coverage
The pattern where a language model emits or selects, as structured output constrained by a typed schema, a specification of a UI component or sub-tree that an application then renders for the user. Covers the three load-bearing contracts (component schema, generation constraint, render pipeline), the implementation variants (direct UI-spec generation, tool-result-to-component binding, hosted-widget bridge, RSC/component-streaming transport), component-palette design rules (semantic discriminated-union types, bounded props, versioning/migration, fallback representation, provider schema-compatibility testing), the two render substrates (typed-component-tree vs sandboxed-iframe HTML), streaming discipline (partial specs drive provisional UI only; final render waits for completed validation), the bidirectional interaction loop (postMessage/JSON-RPC) that closes the cycle from user action back into the model's next turn — including stable per-instance ids for multi-component disambiguation and per-control state-update rules, the UI lifecycle and human-in-the-loop states (idle/thinking/streaming/finalization; propose-edit, gated action, verification loop), the hosted-widget data-channel split (structuredContent/content visible to the model vs _meta component-only) and per-widget domain/CSP policy, the trust boundary that makes the rendering safe under adversarial input, the taxonomy by palette openness / generation mechanism / render substrate / interactivity / trust posture, the 2025–2026 protocol standardization (MCP Apps, OpenAI Apps SDK, MCP-UI, A2UI, AG-UI), and the distinction from adjacent patterns (chat with markdown, tool-call protocol mechanics, prebuilt-widget routing, RSC streaming / AG-UI as transport, AI-assisted design/codegen, model-emits-code which is something else).
Philosophy of the skill
The conventional model-to-user pipeline is: model emits text; user reads text. Generative UI inverts this: model emits a typed component specification; application renders the specification; user sees a UI surface authored by the application's design system but composed by the model.
This indirection is what makes the pattern simultaneously safe and expressive. Safe, because the application owns the rendering — the model can only ask the application to render components/resources the application has chosen to expose, with data the schema permits, and where HTML executes at all it executes inside a sandbox the model cannot escape. Expressive, because the model can compose components, choose presentations per response, and adapt to the question at hand without the developer pre-building every variant.
The discipline is in the three contracts. The schema must be small enough to be a coherent design vocabulary but expressive enough to cover the assistant's responses. The generation constraint must be reliable enough that the model's emission is always renderable. The render pipeline must enforce validation, design-system compliance, and security policies — by restriction (typed component tree) or by isolation (sandboxed iframe) — independently of what the model intended. When all three hold, generative UI lets a language model author the choice of what to show without ever injecting raw markup into the rendering surface.
The practical discipline is to keep three decisions separate. The model may decide what representation is appropriate for this turn. The application decides which component vocabulary exists and what each component means. The renderer decides whether a concrete emitted instance is safe, valid, accessible, and renderable. Collapsing those roles is where the pattern fails: if the model gets to invent components, it has escaped the palette; if schema validity is treated as authorization, unsafe URLs and overlong content still reach the UI; if a click is treated as execution permission, an injected or mistaken UI can trigger real side effects.
The Three Load-Bearing Contracts
Contract
Owner
Defines
Failure mode if absent
Component schema
Application developer
The typed vocabulary the model may emit and the application will render
Model emits unrenderable specs; design inconsistency
Generation constraint
Model + API
The mechanism that forces the model's output to be a valid schema instance
Specs fail validation; retry loops; production flake
Render pipeline
Application
The code that turns a validated spec into pixels with design-system and security policies enforced
Model output reaches the DOM unchecked; security and design break
Implementation Variants
Generative UI ships in several shapes. They share the renderer discipline but differ in what the model emits and what binds it to a component — keep them distinct so a tool-result rendering is not mislabeled open-ended generation.
Variant
What the model emits
What the app renders
Use when
Main risk
Direct UI-spec generation
A discriminated-union instance such as { "type": "chart", "props": … }, A2UI JSON messages, or another declarative UI spec over a known catalog
A component registry maps type/component to application components
The answer shape is open-ended and the model should choose among several presentations
Schema too broad; recursive trees or unsafe props become hard to govern
Tool-result component binding
A tool call plus a typed tool result; the UI maps tool name/result to a component
A fixed component per tool or result type
The UI is a rich rendering of external data fetched by a tool
Mislabeling this as open-ended generative UI; the model chose the tool, not the component tree
Hosted-widget bridge
A tool descriptor declares an output schema plus a UI resource; result structuredContent/content feeds model and component, private _meta hydrates only the component
A sandboxed/iframe widget or host-provided component
Building inside a host such as ChatGPT Apps / MCP Apps
Assuming host sandboxing removes the need for schema, CSP, origin, and action policy
RSC/component-streaming transport
A tool/function call, structured selection, or tool result the framework maps into RSC payloads or UIMessage parts
Framework-managed React components
Low-latency rich chat UI in React/Next.js
Treating transport as the concept; experimental RSC APIs may not be production defaults
The first variant is the cleanest expression of this skill. The others are common industry implementations that use the same renderer discipline, but they should not blur the boundary: tool-call-flow owns the call/result protocol, rendering-models owns the delivery model, and this skill owns the model-to-renderer UI contract.
Generation Mechanisms Compared
Mechanism
How it works
Production stance
Watchouts
Provider-native structured output
The API constrains the final answer to a JSON Schema or schema-like response format (logits masked at decode so only schema-valid tokens emit)
Strong default for direct UI-spec generation when the provider supports the needed schema subset
Every provider supports only a subset; keep the palette shallow, explicit, and compatibility-tested
Strict function/tool calling
The model emits a tool/function call whose arguments are schema-constrained, often through grammar-constrained sampling (strict: true)
Strong default when a UI component is represented as a tool, or when a tool result drives the component
Non-strict, best-effort tool calling is not a reliable render contract — require strict/schema-enforced mode where available
Framework strategy layer
A framework picks provider-native structured output when possible and falls back to a tool-calling strategy (e.g. LangChain ProviderStrategy/ToolStrategy)
Useful portability layer across providers
The framework does not remove the renderer contract; validation, fallback, and safety policy remain application work
Streaming structured output
Partial JSON / UIMessage / tool-argument deltas arrive before the final object completes
Useful for skeletons, progress, and perceived latency
Partial output is not final authority; render provisional UI only and commit after the completed object/spec validates
Free-form JSON / JSON mode + post-validation
The model is asked to emit JSON and the app validates/retries
Prototype fallback only
Valid JSON is not schema adherence; retries add latency and production variance
Production-grade generative UI uses provider-native structured output (OpenAI Structured Outputs, Gemini response schema, Anthropic strict tool use) or strict function/tool calling for the generation-constraint contract. "Function calling" by itself is too broad: a best-effort function call that can omit required fields or invent argument shapes is not a reliable render contract.
Component Palette Design
The component palette is the model-facing API of the UI system. Design it like a public contract, not like an internal React prop bag. A few rules separate a palette that survives production from one that produces constant validation failures and breaking changes:
Model the palette as a discriminated union, not a grab-bag object. A single type/kind discriminator with a per-variant prop shape is what lets both the generation constraint and the render pipeline's component-lookup step be exhaustive and type-checked. An open object with optional everything pushes validation to runtime and invites ambiguous specs.
Keep component types semantic.comparison_table, confirmation_card, date_range_picker, line_chart — not blue_card or two_column_div. The model reasons about meaning, not layout.
Keep props bounded and shallow. Constrain enums, set minLength/maxLength and numeric bounds, allowed URL origins, allowed MIME types, finite nesting depth, and explicit nullability; prefer a shallow tree to deep recursion. Provider structured-output engines impose real limits — OpenAI Structured Outputs and Gemini response schemas cap total schema size, nesting depth, and property counts, and silently degrade or reject schemas that exceed them — so an unbounded palette can become un-emittable on a given model. Deeply nested schemas are also harder to generate, stream progressively, and migrate.
Version the palette and plan migration. Carry a schema/palette version and treat a prop change as an API change: additive (new optional prop, new union variant) is safe; renaming or removing a variant is breaking. Persisted UI specs must either carry the palette version they were generated against or be treated as non-replayable after palette changes. Pin the version the renderer understands so a newer model emitting a newer variant degrades predictably instead of crashing.
Define an explicit fallback representation. For every component, specify what the renderer shows when a spec is unknown, fails validation, or names a variant this renderer version does not ship (e.g. a plain_text summary, aria_label, table_data, or a generic card). "Unknown spec" must be a designed state, not a thrown exception reaching the user — and the fallback is also the accessibility/degradation path when the rich renderer is unavailable.
Give every interactive component a stable component_id or action target so events can be correlated with the rendered instance (see The Interaction Loop).
Do not leak implementation details. The model should not choose CSS classes, Tailwind strings, DOM structure, raw JSX, arbitrary component imports, or framework-specific props unless those are intentionally part of the published palette.
Test provider schema compatibility in CI. Each target provider/model accepts a slightly different JSON Schema dialect and subset (required-ness handling, $ref/anyOf support, format keywords). Add a test that submits the palette schema to every provider you ship on and asserts it is accepted and round-trips, so a schema edit one provider rejects is caught before release, not in production.
Two Render Substrates: Typed-Component-Tree vs Sandboxed-Iframe
A validated spec must be turned into pixels by some substrate. There are two legitimate ones, distinguished by where the safety comes from. Conflating them — or believing only one is "real" generative UI — is the most common 2026-era confusion.
Property
Typed-component-tree
Sandboxed-iframe
What renders
The app's own registered components, selected by the spec's type discriminator
A developer-authored HTML/JS UI resource loaded into a sandboxed <iframe>
Safety comes from
Restriction — the model can only name components in the published vocabulary; no HTML is executed
Isolation + host mediation — the iframe sandbox blocks access to parent DOM, cookies, storage; HTML/JS runs but cannot escape, and the host enforces CSP/domain policy
Who authors the markup
Nobody emits markup; the app maps spec → component
The developer authors the HTML resource once; the model never writes it
What the model authors
A typed selection + data
A tool call that selects a UI resource + the data pushed into it
Interaction transport
App event-routing layer appends the action as a user-role turn (a tool_result or app-defined structured block — no standard provider ui_event content type exists)
postMessage / JSON-RPC bridge (ui/-prefixed methods) between iframe and host
Canonical implementations
A2UI (declarative JSON over a trusted client-held catalog), bespoke React/Vue component registries, Vercel AI SDK RSC streamUI (now paused — see vendor table)
Tight design-system fidelity; small, well-known palettes; no third-party code
Rich/interactive apps (maps, dashboards, 3D, media viewers); third-party servers the host does not fully trust
The shared invariant across both: the model never authors raw HTML injected into the host DOM. In the typed-component substrate it emits a typed spec; in the iframe substrate it emits a tool call that selects a pre-authored resource and supplies data. Unsandboxed model-emits-HTML-into-the-host-page is neither substrate — it is the dangerous anti-pattern both exist to avoid.
A2UI deserves first-class treatment in the typed-component-tree substrate. It is an open-source/open-standard public-preview example where the agent sends declarative JSON messages, the client renders native components from a trusted catalog, and data binding uses JSON-Pointer paths into application state. That makes A2UI a strong example of safety-by-vocabulary-restriction: expressive like UI, but still data interpreted by trusted renderer code rather than model-authored HTML/JSX/CSS/JS. Label its maturity clearly — A2UI is useful current evidence, but its public-preview versions and catalogs can still evolve.
AG-UI is not a third render substrate. It is an agent-to-frontend event transport that can carry message events, tool-lifecycle events (start → progress → finish), state snapshots/deltas, interrupts, and generative-UI specs such as A2UI — but it does not itself render. MCP discovery is likewise not a generation constraint. Keep generation constraint, render substrate, and event transport named separately.
The Render Pipeline
A schema-validated spec is the input to the render pipeline, not permission to render. For the typed-component substrate the pipeline performs:
Parse and schema validation. Parse untrusted model output and validate it against the published JSON Schema or TypeScript type for the component vocabulary. Reject (and either retry or fall back) anything that doesn't match.
Normalize and migrate. Convert provider-specific shapes into the app's canonical UI-spec version; migrate older persisted specs, or refuse replay when no migration exists.
Component lookup. Map the spec's type discriminator to the application's registered component implementation. A spec asking for a component the application doesn't ship is a routing error, not permission to render arbitrary content.
Props validation. Validate props against the component's runtime schema independently of the static type — defense in depth against schema drift and untrusted runtime values.
Safety policy. For every prop that could be a security boundary — image URLs, link targets, embedded content, HTML strings — apply per-component policy: origin allowlists, img-src/connect-src/frame policy, URL/link sanitization, length caps, MIME checks, recursion-depth limits, external-resource controls. The schema describes structure; the safety policy describes acceptable content.
Accessibility and fallback policy. Confirm the target component implementation owns keyboard behavior, focus management, ARIA semantics, labels, reduced-motion behavior, and a fallback rendering for unsupported or inaccessible rich output.
Recursive render. Render the component with the validated props; for nested components in props, recurse through the same pipeline, to finite depth.
Interaction wiring. Wire interactive elements (buttons, inputs) to the application's event-routing layer, which encodes user actions as structured events the model can read in its next turn.
Telemetry and fallback. Log validation failures, unknown component types, safety-policy denials, and fallback usage so the palette and evals can improve.
For the sandboxed-iframe substrate the pipeline is different in shape but identical in intent: fetch the declared ui:// resource (HTML+JS+CSS), render it in a sandboxed <iframe> whose permissions and content-security-policy the host controls (e.g. _meta.ui.csp, _meta.ui.permissions in MCP Apps), push the model's tool-result data into the iframe over the postMessage bridge, and proxy any tool calls the iframe requests back through the host's authorization. In both substrates the model's "intent" does not authorize anything; the pipeline (restriction) or the sandbox + host mediation (isolation) does.
If a provider or agent framework exposes reasoning, thinking, traces, or activity events, do not pipe raw chain-of-thought or private reasoning tokens into the generated UI as content. The UI may show sanitized progress labels, tool/activity traces, or collapsible status events when the product needs latency feedback, but the render contract remains the completed, validated UI spec or tool result.
The hosted-widget data channel (iframe substrate)
A tool result that drives a hosted widget is not one undifferentiated blob — it has three audiences, and conflating them leaks data or starves the component. In the OpenAI Apps SDK / MCP Apps shape:
Channel
Visible to
Use for
structuredContent
Model and the component
The data the model should reason about in its next turn and the component needs to render (e.g. the list of search results behind a results card)
content
Model and the component
The natural-language/text portion the model reads as the tool's textual result
_meta
The component only — never enters the model's context
Render-only payload the model neither needs nor should pay tokens for, and data you do not want influencing the model (full result sets, pagination cursors, presentation hints)
Two rules fall out of the split: (1) put in _meta anything the component needs but the model should not see or be steered by — it is the lever that keeps a large render payload out of the context window and keeps untrusted bulk data from reaching the model; (2) anything the model must reason about in the next turn has to live in structuredContent/content, because _meta is invisible to it. Hidden-from-the-model data can still be visible to the component and browser environment — protect it according to the component's threat model. The host also enforces a per-widget domain/CSP policy (declared resource origins, allowed connect-src/frame-src): a widget may only load and call out to origins the host has allowlisted, so a compromised or malicious resource cannot exfiltrate to an arbitrary endpoint. UI-to-host messages travel through a declared bridge (JSON-RPC over postMessage); every callable method needs schema validation, origin/trust labeling, logging, and user-consent policy where appropriate. The data-channel split and the domain policy together are why the iframe substrate can host third-party, not-fully-trusted UI without handing it the model's context or the open network.
Streaming Discipline
A spec usually arrives incrementally — partial JSON, partial tool-call arguments, or streamed UI deltas (Anthropic fine-grained tool streaming, the AI SDK experimental_output / partial-object stream, Gemini partial structured responses). Partial output is a latency affordance, not a render authorization. The discipline:
A partial spec may drive only provisional UI — a skeleton, a loading placeholder, or an optimistic shell keyed off the discriminator once it is known. It must be visibly non-final.
Final rendering waits for completed validation. The full spec must arrive, parse, and pass the same schema + props + safety-policy validation as the non-streamed path before any committed, interactive, or side-effecting component is mounted. Rendering off half-parsed JSON risks XSS through an un-sanitized prop, mounting a destructive action before its guard fields exist, or flicker/remount when the tail of the stream changes an earlier field.
Never wire interaction to a provisional render. Buttons and inputs become live only after validation completes — a click on an optimistic shell has no validated action shape to encode.
The same rule holds in the iframe substrate: stream the model's data into the iframe as it arrives if the resource is built for it, but the host gates committed tool calls on completed, validated payloads, not on partial deltas.
The Interaction Loop
For interactive generative UI, every interactive component needs five definitions:
Definition
Example for a Confirm button
Why it matters
Instance identity
{ component_id: "confirm-17" }
A response that renders several of the same component needs each event to identify which rendered instance the user acted on
The model receives a typed event in the next turn, not an ambiguous natural-language click
History encoding
A user-role turn carrying the action — e.g. a tool_result block keyed to the originating tool-use, or an application-defined structured content block. There is no standard provider ui_event content type; an { type: 'ui_event', … } shape is an application convention, not a wire format. The MCP Apps dialect instead carries it as a ui/-prefixed JSON-RPC message over postMessage
The model reads it the same way it reads any other turn
Side-effect policy
Confirming requires an additional tool call gated by user identity, never auto-executed from the click
The click is a user signal, not authorization for the destructive operation
State-update rule
The component declares whether it is optimistic, disabled-while-pending, or waits for model/tool confirmation
The UI remains coherent while the next model/tool turn runs
Instance identity. A response that renders several interactive components (three confirm cards, a list of editable rows) needs each emitted event to carry a stable per-instance id (component_id) so the model can correlate the event to the right rendered instance and the data that produced it. Without it, multi-component responses produce ambiguous events the model cannot route.
A generative UI with interactive components but no interaction-loop definition is one-way: the user clicks and nothing happens. A generative UI that auto-executes destructive actions on click is a prompt-injection-defense failure waiting to happen. The middle path — click produces a structured event the model reasons about, the model emits a tool call that goes through normal authorization, idempotency, and audit controls — is the discipline.
Host-mediated capability delegation. In the protocol-standardized world (MCP Apps, OpenAI Apps SDK), the embedded UI does not act on the world directly. It requests an outcome ("schedule this meeting", "send this email") and the host routes it through capabilities the user has already connected, subject to user consent. This is the same discipline at the protocol layer: the rendered surface emits a request, the trusted host applies authorization, and side effects happen only through gated, user-consented paths — never because a button was clicked inside a model-influenced surface.
UI Lifecycle And Human-In-The-Loop States
A generative-UI surface is not a single render — it moves through a lifecycle, and with reasoning models the latency between "user asked" and "UI is final" is long enough that each phase needs its own designed state. Event-transport protocols (AG-UI's run/message/tool-lifecycle/state events) exist precisely to carry these phase transitions to the frontend. The phases:
Phase
What the surface shows
Contract rule
Idle / input
The affordance to ask, empty states, suggested starters, or the last settled result
Do not pre-render a component that implies model certainty before the model commits to a representation
Thinking / progress
A non-final working indicator — reasoning/tool-call progress, status text, partial plan
Show sanitized progress labels or tool/activity events; do not expose raw chain-of-thought or private reasoning tokens as user-facing UI content
Streaming / provisional
Skeletons and optimistic shells keyed off the discriminator (see Streaming Discipline) — visibly non-final, interaction not yet wired
Treat deltas as provisional; no final commit or side-effect affordance until completed validation passes
Revision / finalization
The validated, interactive render; earlier provisional UI is replaced, not appended
Encode user edits/approvals as typed events; state-changing work still goes through authorized tool/server actions
On top of the phases sit three human-in-the-loop (HITL) patterns, each a different answer to "who commits?":
Propose-edit. The model proposes a populated surface (a drafted email, a filled form, a diff, an itinerary, a configuration); the user edits fields in place and approves. The model's output is a starting point, not a committed action — the edited values, not the proposed ones, flow back as the action event.
Gated action. A rendered control whose effect is withheld until explicit user confirmation. The click produces the structured event; the destructive/external effect is a separate, user-identity-gated tool call (this is the side-effect policy of the interaction loop, surfaced as a UI state).
Verification loop. The model emits a result, the user reviews and accepts/rejects/annotates, and the rejection or annotation is encoded back as a turn the model reasons about — turning a one-shot render into an iterative, user-supervised refinement.
The unifying rule across phases and HITL patterns: a non-final state must be visibly non-final and must not be wired to side effects. Provisional UI that looks committed, or a proposed action that fires before the user approves, is the lifecycle-level form of the same failure the interaction loop guards against at the event level.
Upstream Displacement Check
Current upstream state does not make this skill obsolete. It changes which implementations are the best examples:
OpenAI's current API framing is Responses-API structured output / function calling plus Apps SDK widgets — not new work on the older Assistants API as the canonical path.
MCP Apps is now a portable hosted-widget standard (official 2026-01-26 extension: ui:// resources, tool linkage, sandboxed iframes, JSON-RPC/postMessage), not just a vendor-local trick.
A2UI is a current public-preview/open-standard example of the typed-component-tree substrate (declarative JSON over trusted catalogs, JSON-Pointer data binding, native rendering).
AG-UI is useful context for evented agent/frontend interaction and HITL flows, but it is a transport, not a generation mechanism and not a renderer.
Anthropic now documents strict tool use as grammar-constrained schema adherence; non-strict tool use should not be used as the production render contract.
Vercel's current production path is AI SDK UI generative interfaces (UIMessage/tool parts); AI SDK RSC streamUI remains a useful example but Vercel's own docs call RSC experimental and recommend AI SDK UI for production.
The durable rule: prefer the strongest provider-native or strict-tool mechanism available, but keep the portable renderer contract independent of any one vendor — the pattern predates and outlives any single implementation.
Provider-native schema-constrained final output (grammar-constrained decoding against JSON Schema)
Current — strong for direct UI-spec generation; prefer over JSON mode
Responses-API function tools with strict: true
OpenAI
Strict JSON-Schema function-call arguments
Current — strong for component-as-tool or tool-result binding
OpenAI Apps SDK (window.openai, apps-sdk-ui)
OpenAI
Sandboxed-iframe components over MCP; host bridge for toolInput/toolOutput/setWidgetState/callTool/requestDisplayMode; inline-card/carousel/fullscreen display modes; transcript-visible data vs component-only _meta split
Current — still requires CSP, origin, and action policy; prefer the ui/* MCP-Apps bridge methods over legacy window.openai-only calls where both exist
MCP Apps (@modelcontextprotocol/ext-apps)
Model Context Protocol
Sandboxed-iframe HTML resource (ui://) + postMessage/JSON-RPC bridge; CSP/domain policy
First official MCP extension, ratified 2026-01-26; launch blog names ChatGPT, Claude, Goose, and VS Code as clients (MCP-UI adopters such as Postman/Shopify are a broader, separate set — do not conflate)
MCP-UI (@mcp-ui/client)
MCP-UI community / Shopify et al.
Three render modes: raw HTML, external URL, remote-DOM
Current — the rendering library behind many MCP Apps hosts; remote-DOM avoids iframe overhead but needs client-side component-library coordination
Anthropic strict tool use
Anthropic
Grammar-constrained schema adherence for tool inputs
Current — strong for schema-valid component/tool arguments
Anthropic fine-grained tool streaming
Anthropic
Streams tool-input deltas before buffering/validation
Current — latency feature; handle invalid/partial JSON and commit only after validation
Gemini response schema
Google
Response constrained to a JSON Schema subset
Current — strong for direct UI-spec generation; still validate semantic/business correctness in app code
A2UI
Google / A2UI project
Declarative JSON messages over a client-held component catalog, JSON-Pointer data binding, native renderer mapping
Current public-preview / open standard — first-class typed-component-tree (restriction) substrate; label maturity clearly
AG-UI protocol + A2UI
CopilotKit (adopted by Google, LangChain, AWS, Microsoft, Mastra, PydanticAI)
Event-based agent↔frontend transport (message/tool-lifecycle/state events); carries generative-UI specs including A2UI
Current — transport, distinct from the rendering substrate; visualize traces/tool events, not raw chain-of-thought
AI SDK UI generative user interfaces
Vercel
Tool calls/results rendered as typed React UI parts (UIMessage/tool parts)
Current — Vercel's production-oriented recommendation
Vercel AI SDK RSC (streamUI)
Vercel
RSC streaming of components selected by function calling
Paused / experimental — not for production. Known failure modes: stream-completion flicker/remount, multiple-suspense-boundary crashes, createStreamableUI quadratic data transfer; migrate to AI SDK UI (useChat); the older render API is removed
LangChain structured + frontend structured output
LangChain
ProviderStrategy vs ToolStrategy; validated structured response mapped to UI with progressive rendering and fallbacks
Current — portability layer; renderer still owns validation, fallback, and safety
Generic JSON mode / OpenRouter-compatible JSON
Multiple
Free-form JSON with post-validation, falling back to retry
Current — prototype-grade; not a production render contract by itself
Citing one vendor's API as the canonical pattern is a category error; the pattern predates and outlives any single implementation. The 2025–2026 center of gravity shifted toward MCP-based sandboxed-iframe apps (MCP Apps, OpenAI Apps SDK), open typed-component standards (A2UI), and event-transport protocols (AG-UI). Use whichever substrate + mechanism the chosen host and model support most reliably.
Verification
After applying this skill, verify:
The component palette is a published, typed schema (JSON Schema, TypeScript discriminated union, or equivalent) — or, in the iframe substrate, a registered set of ui:// resources — not an informal convention.
The palette is a discriminated union with semantic type names and bounded, shallow props; it stays within every target provider's structured-output size/depth limits, carries a version, and has a CI test asserting each provider accepts and round-trips it.
Provider schema compatibility is tested against the actual model/API, not assumed from full JSON Schema; unsupported keywords are removed or replaced deliberately, and provider-specific subsets do not leak into a portable palette.
Every component has an explicit fallback representation (plain-text/summary/aria) for unknown / validation-failed / newer-than-renderer specs, and for when accessibility requirements cannot be met — "unknown spec" is a designed state, not an uncaught exception reaching the user.
The generation mechanism is provider-native structured output or strict/schema-enforced tool calling — not best-effort function calls and not free-form-with-retry in production.
Streamed/partial specs drive only visibly-provisional UI (skeletons, placeholders); committed, interactive, or side-effecting components mount only after the full spec passes validation, and interaction is wired only post-validation.
The render substrate is explicitly chosen (typed-component-tree vs sandboxed-iframe) and its safety basis named (restriction vs isolation).
Every spec the model emits is parsed and validated against the schema by the render pipeline before any component is invoked — the model's emission alone does not authorize rendering.
In the typed-component substrate, per-component safety policies are enforced: image URLs pass an origin allowlist; link targets are sanitized; content-length is bounded; nested specs terminate in finite depth.
In the sandboxed-iframe substrate, the iframe sandbox blocks parent-DOM/cookie/storage access; the host controls the iframe's permissions and CSP/domain policy; external opens are allowlisted; tool calls the iframe requests are proxied through host authorization, not granted by the iframe.
For hosted widgets, the tool result splits its audiences correctly: data the model must reason about is in structuredContent/content; render-only or bulk/untrusted data the model should not see (or pay tokens for) is in _meta (component-only).
Raw chain-of-thought / private reasoning tokens are never piped into the UI as content; only sanitized progress labels or tool/activity traces are shown.
Each lifecycle phase has a designed, visibly-non-final state (thinking/progress, streaming/provisional) distinct from the finalized render; the chosen HITL pattern (propose-edit, gated action, or verification loop) is explicit, and no non-final state is wired to a side effect.
Every interactive component has a stable per-instance id, a defined action shape, a history encoding (a user-role tool_result/app-defined block, or a ui/ postMessage; not a standard provider ui_event content type, which does not exist), a side-effect policy, and a state-update rule. Auto-executing destructive actions on rendered-component clicks is forbidden; capability delegation routes through the host with user consent.
Tool-result-to-component binding is named as such when the model chose a tool rather than an arbitrary UI tree.
The model never authors raw HTML injected into the host DOM. What it authors is a typed selection plus data — in the iframe substrate the HTML resource is developer-authored, not model-written.
The pattern is used for open-ended assistant responses, not for known repeated workflows where traditional UI design is the better fit.
Accessibility (keyboard navigation, ARIA roles, focus management) is the responsibility of the application's component implementations (or, in the iframe substrate, the embedded app) — generative UI does not produce accessible UI for free.
Vendor examples in docs name their maturity: production-oriented, experimental, hosted-platform-specific, or prototype-only.
Do NOT Use When
Instead of this skill
Use
Why
Designing the page-level rendering model (CSR vs SSR vs RSC vs streaming)
rendering-models
re
…(truncated)
1---2name: generative-ui3description: Use when reasoning about the pattern where a language model emits structured output describing UI components or a UI sub-tree that an application renders for the user. Covers the typed-schema component palette, JSON Schema/function-calling constraints, two render substrates (typed component tree vs sandboxed iframe), the app-side render pipeline, bidirectional interaction loop via postMessage/JSON-RPC, the security boundary between model author and application renderer, and distinctions from chat markdown, prebuilt-widget routing, RSC streaming, and model-emits-code patterns. Do NOT use for page-level rendering taxonomy (use rendering-models), the tool-call protocol cycle (use tool-call-flow), untrusted-content defenses (use prompt-injection-defense), or general component-library architecture (use design-system-architecture). Do NOT use for design the JSON shape of an HTTP API endpoint (use api-design).4license: MIT5---6# Generative UI78## Concept of the skill910Generative UI is the pattern in which a language model emits — as structured output constrained by a typed schema — a *specification* of a UI component or sub-tree that an application then renders for the user. The load-bearing distinction is that the model's output is neither a chat response, nor a tool call asking for execution, nor raw markup it freely authors: it is a typed instance of a component-vocabulary schema, plus the data to populate it. Three contracts must hold simultaneously for the pattern to work — the **component schema** (the typed vocabulary the model and application share), the **generation constraint** (the mechanism that forces the model's emission to be a valid schema instance — provider-native structured output or strict tool calling, not best-effort function calls), and the **render pipeline** (the application code that turns a validated spec into pixels). Rendering happens through one of two legitimate substrates that differ in where safety comes from: a *typed-component-tree* where safety is by **restriction** (the model can only name components in the published vocabulary; no HTML executes), or a *sandboxed-iframe* where safety is by **isolation plus host mediation** (a developer-authored HTML resource runs in a sandbox that cannot reach the parent DOM, fed the model's data over a postMessage/JSON-RPC bridge). The invariant across both is that the model never authors raw HTML injected into the host DOM — it authors a typed selection and data. The application owns rendering, the interaction layer, and visual design; the model owns the choice of which components to compose and with what data. A bidirectional interaction loop closes the cycle: a user action on a rendered component becomes a typed event the application encodes as the next turn the model reasons about, with destructive effects gated as separate, user-identity-authorized tool calls rather than auto-executed from a click.1112## Coverage1314The pattern where a language model emits or selects, as structured output constrained by a typed schema, a specification of a UI component or sub-tree that an application then renders for the user. Covers the three load-bearing contracts (component schema, generation constraint, render pipeline), the implementation variants (direct UI-spec generation, tool-result-to-component binding, hosted-widget bridge, RSC/component-streaming transport), component-palette design rules (semantic discriminated-union types, bounded props, versioning/migration, fallback representation, provider schema-compatibility testing), the two render substrates (typed-component-tree vs sandboxed-iframe HTML), streaming discipline (partial specs drive provisional UI only; final render waits for completed validation), the bidirectional interaction loop (postMessage/JSON-RPC) that closes the cycle from user action back into the model's next turn — including stable per-instance ids for multi-component disambiguation and per-control state-update rules, the UI lifecycle and human-in-the-loop states (idle/thinking/streaming/finalization; propose-edit, gated action, verification loop), the hosted-widget data-channel split (`structuredContent`/`content` visible to the model vs `_meta` component-only) and per-widget domain/CSP policy, the trust boundary that makes the rendering safe under adversarial input, the taxonomy by palette openness / generation mechanism / render substrate / interactivity / trust posture, the 2025–2026 protocol standardization (MCP Apps, OpenAI Apps SDK, MCP-UI, A2UI, AG-UI), and the distinction from adjacent patterns (chat with markdown, tool-call protocol mechanics, prebuilt-widget routing, RSC streaming / AG-UI as transport, AI-assisted design/codegen, model-emits-code which is something else).1516## Philosophy of the skill1718The conventional model-to-user pipeline is: model emits text; user reads text. Generative UI inverts this: model emits a typed component specification; application renders the specification; user sees a UI surface authored by the application's design system but composed by the model.1920This indirection is what makes the pattern simultaneously safe and expressive. Safe, because the application owns the rendering — the model can only ask the application to render components/resources the application has chosen to expose, with data the schema permits, and where HTML executes at all it executes inside a sandbox the model cannot escape. Expressive, because the model can compose components, choose presentations per response, and adapt to the question at hand without the developer pre-building every variant.2122The discipline is in the three contracts. The schema must be small enough to be a coherent design vocabulary but expressive enough to cover the assistant's responses. The generation constraint must be reliable enough that the model's emission is always renderable. The render pipeline must enforce validation, design-system compliance, and security policies — by *restriction* (typed component tree) or by *isolation* (sandboxed iframe) — independently of what the model intended. When all three hold, generative UI lets a language model author the choice of what to show without ever injecting raw markup into the rendering surface.2324The practical discipline is to keep three decisions separate. The model may decide *what representation is appropriate* for this turn. The application decides *which component vocabulary exists and what each component means*. The renderer decides *whether a concrete emitted instance is safe, valid, accessible, and renderable*. Collapsing those roles is where the pattern fails: if the model gets to invent components, it has escaped the palette; if schema validity is treated as authorization, unsafe URLs and overlong content still reach the UI; if a click is treated as execution permission, an injected or mistaken UI can trigger real side effects.2526## The Three Load-Bearing Contracts2728| Contract | Owner | Defines | Failure mode if absent |29|---|---|---|---|30| Component schema | Application developer | The typed vocabulary the model may emit and the application will render | Model emits unrenderable specs; design inconsistency |31| Generation constraint | Model + API | The mechanism that forces the model's output to be a valid schema instance | Specs fail validation; retry loops; production flake |32| Render pipeline | Application | The code that turns a validated spec into pixels with design-system and security policies enforced | Model output reaches the DOM unchecked; security and design break |3334## Implementation Variants3536Generative UI ships in several shapes. They share the renderer discipline but differ in *what the model emits* and *what binds it to a component* — keep them distinct so a tool-result rendering is not mislabeled open-ended generation.3738| Variant | What the model emits | What the app renders | Use when | Main risk |39|---|---|---|---|---|40| Direct UI-spec generation | A discriminated-union instance such as `{ "type": "chart", "props": … }`, A2UI JSON messages, or another declarative UI spec over a known catalog | A component registry maps `type`/`component` to application components | The answer shape is open-ended and the model should choose among several presentations | Schema too broad; recursive trees or unsafe props become hard to govern |41| Tool-result component binding | A tool call plus a typed tool result; the UI maps tool name/result to a component | A fixed component per tool or result type | The UI is a rich rendering of external data fetched by a tool | Mislabeling this as open-ended generative UI; the model chose the tool, not the component tree |42| Hosted-widget bridge | A tool descriptor declares an output schema plus a UI resource; result `structuredContent`/`content` feeds model and component, private `_meta` hydrates only the component | A sandboxed/iframe widget or host-provided component | Building inside a host such as ChatGPT Apps / MCP Apps | Assuming host sandboxing removes the need for schema, CSP, origin, and action policy |43| RSC/component-streaming transport | A tool/function call, structured selection, or tool result the framework maps into RSC payloads or UIMessage parts | Framework-managed React components | Low-latency rich chat UI in React/Next.js | Treating transport as the concept; experimental RSC APIs may not be production defaults |4445The first variant is the cleanest expression of this skill. The others are common industry implementations that use the same renderer discipline, but they should not blur the boundary: tool-call-flow owns the call/result protocol, rendering-models owns the delivery model, and this skill owns the model-to-renderer UI contract.4647## Generation Mechanisms Compared4849| Mechanism | How it works | Production stance | Watchouts |50|---|---|---|---|51| Provider-native structured output | The API constrains the final answer to a JSON Schema or schema-like response format (logits masked at decode so only schema-valid tokens emit) | Strong default for direct UI-spec generation when the provider supports the needed schema subset | Every provider supports only a subset; keep the palette shallow, explicit, and compatibility-tested |52| Strict function/tool calling | The model emits a tool/function call whose arguments are schema-constrained, often through grammar-constrained sampling (`strict: true`) | Strong default when a UI component is represented as a tool, or when a tool result drives the component | Non-strict, best-effort tool calling is *not* a reliable render contract — require strict/schema-enforced mode where available |53| Framework strategy layer | A framework picks provider-native structured output when possible and falls back to a tool-calling strategy (e.g. LangChain ProviderStrategy/ToolStrategy) | Useful portability layer across providers | The framework does not remove the renderer contract; validation, fallback, and safety policy remain application work |54| Streaming structured output | Partial JSON / UIMessage / tool-argument deltas arrive before the final object completes | Useful for skeletons, progress, and perceived latency | Partial output is not final authority; render provisional UI only and commit after the completed object/spec validates |55| Free-form JSON / JSON mode + post-validation | The model is asked to emit JSON and the app validates/retries | Prototype fallback only | Valid JSON is not schema adherence; retries add latency and production variance |5657Production-grade generative UI uses provider-native structured output (OpenAI Structured Outputs, Gemini response schema, Anthropic strict tool use) or strict function/tool calling for the generation-constraint contract. "Function calling" by itself is too broad: a best-effort function call that can omit required fields or invent argument shapes is not a reliable render contract.5859## Component Palette Design6061The component palette is the model-facing API of the UI system. Design it like a public contract, not like an internal React prop bag. A few rules separate a palette that survives production from one that produces constant validation failures and breaking changes:6263- **Model the palette as a discriminated union, not a grab-bag object.** A single `type`/`kind` discriminator with a per-variant prop shape is what lets both the generation constraint and the render pipeline's component-lookup step be exhaustive and type-checked. An open object with optional everything pushes validation to runtime and invites ambiguous specs.64- **Keep component types semantic.** `comparison_table`, `confirmation_card`, `date_range_picker`, `line_chart` — not `blue_card` or `two_column_div`. The model reasons about meaning, not layout.65- **Keep props bounded and shallow.** Constrain enums, set `minLength`/`maxLength` and numeric bounds, allowed URL origins, allowed MIME types, finite nesting depth, and explicit nullability; prefer a shallow tree to deep recursion. Provider structured-output engines impose real limits — OpenAI Structured Outputs and Gemini response schemas cap total schema size, nesting depth, and property counts, and silently degrade or reject schemas that exceed them — so an unbounded palette can become un-emittable on a given model. Deeply nested schemas are also harder to generate, stream progressively, and migrate.66- **Version the palette and plan migration.** Carry a schema/palette version and treat a prop change as an API change: additive (new optional prop, new union variant) is safe; renaming or removing a variant is breaking. Persisted UI specs must either carry the palette version they were generated against or be treated as non-replayable after palette changes. Pin the version the renderer understands so a newer model emitting a newer variant degrades predictably instead of crashing.67- **Define an explicit fallback representation.** For every component, specify what the renderer shows when a spec is unknown, fails validation, or names a variant this renderer version does not ship (e.g. a `plain_text` summary, `aria_label`, `table_data`, or a generic card). "Unknown spec" must be a designed state, not a thrown exception reaching the user — and the fallback is also the accessibility/degradation path when the rich renderer is unavailable.68- **Give every interactive component a stable `component_id` or action target** so events can be correlated with the rendered instance (see The Interaction Loop).69- **Do not leak implementation details.** The model should not choose CSS classes, Tailwind strings, DOM structure, raw JSX, arbitrary component imports, or framework-specific props unless those are intentionally part of the published palette.70- **Test provider schema compatibility in CI.** Each target provider/model accepts a slightly different JSON Schema dialect and subset (required-ness handling, `$ref`/`anyOf` support, format keywords). Add a test that submits the palette schema to every provider you ship on and asserts it is accepted and round-trips, so a schema edit one provider rejects is caught before release, not in production.7172## Two Render Substrates: Typed-Component-Tree vs Sandboxed-Iframe7374A validated spec must be turned into pixels by *some* substrate. There are two legitimate ones, distinguished by **where the safety comes from**. Conflating them — or believing only one is "real" generative UI — is the most common 2026-era confusion.7576| Property | Typed-component-tree | Sandboxed-iframe |77|---|---|---|78| What renders | The app's own registered components, selected by the spec's `type` discriminator | A developer-authored HTML/JS UI resource loaded into a sandboxed `<iframe>` |79| Safety comes from | **Restriction** — the model can only name components in the published vocabulary; no HTML is executed | **Isolation + host mediation** — the iframe sandbox blocks access to parent DOM, cookies, storage; HTML/JS runs but cannot escape, and the host enforces CSP/domain policy |80| Who authors the markup | Nobody emits markup; the app maps spec → component | The *developer* authors the HTML resource once; the model never writes it |81| What the model authors | A typed selection + data | A tool call that selects a UI resource + the data pushed into it |82| Interaction transport | App event-routing layer appends the action as a user-role turn (a `tool_result` or app-defined structured block — no standard provider `ui_event` content type exists) | postMessage / JSON-RPC bridge (`ui/`-prefixed methods) between iframe and host |83| Canonical implementations | A2UI (declarative JSON over a trusted client-held catalog), bespoke React/Vue component registries, Vercel AI SDK RSC `streamUI` (now paused — see vendor table) | MCP Apps (official MCP extension, 2026-01-26), OpenAI Apps SDK, MCP-UI |84| Best for | Tight design-system fidelity; small, well-known palettes; no third-party code | Rich/interactive apps (maps, dashboards, 3D, media viewers); third-party servers the host does not fully trust |8586The shared invariant across both: **the model never authors raw HTML injected into the host DOM.** In the typed-component substrate it emits a typed spec; in the iframe substrate it emits a tool call that selects a *pre-authored* resource and supplies data. Unsandboxed model-emits-HTML-into-the-host-page is neither substrate — it is the dangerous anti-pattern both exist to avoid.8788**A2UI deserves first-class treatment in the typed-component-tree substrate.** It is an open-source/open-standard public-preview example where the agent sends declarative JSON messages, the client renders native components from a *trusted catalog*, and data binding uses JSON-Pointer paths into application state. That makes A2UI a strong example of safety-by-vocabulary-restriction: expressive like UI, but still *data interpreted by trusted renderer code* rather than model-authored HTML/JSX/CSS/JS. Label its maturity clearly — A2UI is useful current evidence, but its public-preview versions and catalogs can still evolve.8990**AG-UI is not a third render substrate.** It is an agent-to-frontend *event transport* that can carry message events, tool-lifecycle events (start → progress → finish), state snapshots/deltas, interrupts, and generative-UI specs such as A2UI — but it does not itself render. MCP discovery is likewise not a generation constraint. Keep *generation constraint*, *render substrate*, and *event transport* named separately.9192## The Render Pipeline9394A schema-validated spec is the *input* to the render pipeline, not permission to render. For the **typed-component substrate** the pipeline performs:95961. **Parse and schema validation.** Parse untrusted model output and validate it against the published JSON Schema or TypeScript type for the component vocabulary. Reject (and either retry or fall back) anything that doesn't match.972. **Normalize and migrate.** Convert provider-specific shapes into the app's canonical UI-spec version; migrate older persisted specs, or refuse replay when no migration exists.983. **Component lookup.** Map the spec's `type` discriminator to the application's registered component implementation. A spec asking for a component the application doesn't ship is a routing error, not permission to render arbitrary content.994. **Props validation.** Validate props against the component's runtime schema independently of the static type — defense in depth against schema drift and untrusted runtime values.1005. **Safety policy.** For every prop that could be a security boundary — image URLs, link targets, embedded content, HTML strings — apply per-component policy: origin allowlists, `img-src`/`connect-src`/frame policy, URL/link sanitization, length caps, MIME checks, recursion-depth limits, external-resource controls. The schema describes structure; the safety policy describes acceptable content.1016. **Accessibility and fallback policy.** Confirm the target component implementation owns keyboard behavior, focus management, ARIA semantics, labels, reduced-motion behavior, and a fallback rendering for unsupported or inaccessible rich output.1027. **Recursive render.** Render the component with the validated props; for nested components in props, recurse *through the same pipeline*, to finite depth.1038. **Interaction wiring.** Wire interactive elements (buttons, inputs) to the application's event-routing layer, which encodes user actions as structured events the model can read in its next turn.1049. **Telemetry and fallback.** Log validation failures, unknown component types, safety-policy denials, and fallback usage so the palette and evals can improve.105106For the **sandboxed-iframe substrate** the pipeline is different in shape but identical in intent: fetch the declared `ui://` resource (HTML+JS+CSS), render it in a sandboxed `<iframe>` whose permissions and content-security-policy the host controls (e.g. `_meta.ui.csp`, `_meta.ui.permissions` in MCP Apps), push the model's tool-result data into the iframe over the postMessage bridge, and proxy any tool calls the iframe requests back through the host's authorization. In both substrates the model's "intent" does not authorize anything; the pipeline (restriction) or the sandbox + host mediation (isolation) does.107108If a provider or agent framework exposes reasoning, thinking, traces, or activity events, **do not pipe raw chain-of-thought or private reasoning tokens into the generated UI as content.** The UI may show sanitized progress labels, tool/activity traces, or collapsible status events when the product needs latency feedback, but the render contract remains the completed, validated UI spec or tool result.109110### The hosted-widget data channel (iframe substrate)111112A tool result that drives a hosted widget is not one undifferentiated blob — it has *three audiences*, and conflating them leaks data or starves the component. In the OpenAI Apps SDK / MCP Apps shape:113114| Channel | Visible to | Use for |115|---|---|---|116| `structuredContent` | Model **and** the component | The data the model should reason about in its next turn *and* the component needs to render (e.g. the list of search results behind a results card) |117| `content` | Model **and** the component | The natural-language/text portion the model reads as the tool's textual result |118| `_meta` | The **component only** — never enters the model's context | Render-only payload the model neither needs nor should pay tokens for, and data you do not want influencing the model (full result sets, pagination cursors, presentation hints) |119120Two rules fall out of the split: (1) put in `_meta` anything the component needs but the model should not see or be steered by — it is the lever that keeps a large render payload out of the context window and keeps untrusted bulk data from reaching the model; (2) anything the *model* must reason about in the next turn has to live in `structuredContent`/`content`, because `_meta` is invisible to it. Hidden-from-the-model data can still be visible to the component and browser environment — protect it according to the component's threat model. The host also enforces a **per-widget domain/CSP policy** (declared resource origins, allowed `connect-src`/`frame-src`): a widget may only load and call out to origins the host has allowlisted, so a compromised or malicious resource cannot exfiltrate to an arbitrary endpoint. UI-to-host messages travel through a declared bridge (JSON-RPC over postMessage); every callable method needs schema validation, origin/trust labeling, logging, and user-consent policy where appropriate. The data-channel split and the domain policy together are why the iframe substrate can host third-party, not-fully-trusted UI without handing it the model's context or the open network.121122## Streaming Discipline123124A spec usually arrives incrementally — partial JSON, partial tool-call arguments, or streamed UI deltas (Anthropic fine-grained tool streaming, the AI SDK `experimental_output` / partial-object stream, Gemini partial structured responses). Partial output is a *latency* affordance, not a render authorization. The discipline:125126- **A partial spec may drive only provisional UI** — a skeleton, a loading placeholder, or an optimistic shell keyed off the discriminator once it is known. It must be visibly non-final.127- **Final rendering waits for completed validation.** The full spec must arrive, parse, and pass the same schema + props + safety-policy validation as the non-streamed path before any committed, interactive, or side-effecting component is mounted. Rendering off half-parsed JSON risks XSS through an un-sanitized prop, mounting a destructive action before its guard fields exist, or flicker/remount when the tail of the stream changes an earlier field.128- **Never wire interaction to a provisional render.** Buttons and inputs become live only after validation completes — a click on an optimistic shell has no validated action shape to encode.129130The same rule holds in the iframe substrate: stream the model's data into the iframe as it arrives if the resource is built for it, but the host gates committed tool calls on completed, validated payloads, not on partial deltas.131132## The Interaction Loop133134For interactive generative UI, every interactive component needs five definitions:135136| Definition | Example for a Confirm button | Why it matters |137|---|---|---|138| Instance identity | `{ component_id: "confirm-17" }` | A response that renders several of the same component needs each event to identify *which* rendered instance the user acted on |139| Action shape | `{ kind: "confirm", component_id: "confirm-17", accepted: true }` | The model receives a typed event in the next turn, not an ambiguous natural-language click |140| History encoding | A user-role turn carrying the action — e.g. a `tool_result` block keyed to the originating tool-use, or an application-defined structured content block. There is **no** standard provider `ui_event` content type; an `{ type: 'ui_event', … }` shape is an application convention, not a wire format. The MCP Apps dialect instead carries it as a `ui/`-prefixed JSON-RPC message over postMessage | The model reads it the same way it reads any other turn |141| Side-effect policy | Confirming requires an additional tool call gated by user identity, never auto-executed from the click | The click is a user signal, not authorization for the destructive operation |142| State-update rule | The component declares whether it is optimistic, disabled-while-pending, or waits for model/tool confirmation | The UI remains coherent while the next model/tool turn runs |143144**Instance identity.** A response that renders several interactive components (three confirm cards, a list of editable rows) needs each emitted event to carry a stable per-instance id (`component_id`) so the model can correlate the event to the right rendered instance and the data that produced it. Without it, multi-component responses produce ambiguous events the model cannot route.145146A generative UI with interactive components but no interaction-loop definition is one-way: the user clicks and nothing happens. A generative UI that auto-executes destructive actions on click is a prompt-injection-defense failure waiting to happen. The middle path — click produces a structured event the model reasons about, the model emits a tool call that goes through normal authorization, idempotency, and audit controls — is the discipline.147148**Host-mediated capability delegation.** In the protocol-standardized world (MCP Apps, OpenAI Apps SDK), the embedded UI does not act on the world directly. It requests an *outcome* ("schedule this meeting", "send this email") and the **host** routes it through capabilities the user has already connected, subject to user consent. This is the same discipline at the protocol layer: the rendered surface emits a request, the trusted host applies authorization, and side effects happen only through gated, user-consented paths — never because a button was clicked inside a model-influenced surface.149150## UI Lifecycle And Human-In-The-Loop States151152A generative-UI surface is not a single render — it moves through a *lifecycle*, and with reasoning models the latency between "user asked" and "UI is final" is long enough that each phase needs its own designed state. Event-transport protocols (AG-UI's run/message/tool-lifecycle/state events) exist precisely to carry these phase transitions to the frontend. The phases:153154| Phase | What the surface shows | Contract rule |155|---|---|---|156| **Idle / input** | The affordance to ask, empty states, suggested starters, or the last settled result | Do not pre-render a component that implies model certainty before the model commits to a representation |157| **Thinking / progress** | A non-final working indicator — reasoning/tool-call progress, status text, partial plan | Show sanitized progress labels or tool/activity events; **do not expose raw chain-of-thought or private reasoning tokens** as user-facing UI content |158| **Streaming / provisional** | Skeletons and optimistic shells keyed off the discriminator (see Streaming Discipline) — visibly non-final, interaction not yet wired | Treat deltas as provisional; no final commit or side-effect affordance until completed validation passes |159| **Revision / finalization** | The validated, interactive render; earlier provisional UI is replaced, not appended | Encode user edits/approvals as typed events; state-changing work still goes through authorized tool/server actions |160161On top of the phases sit three **human-in-the-loop (HITL)** patterns, each a different answer to "who commits?":162163- **Propose-edit.** The model proposes a populated surface (a drafted email, a filled form, a diff, an itinerary, a configuration); the user edits fields in place and approves. The model's output is a *starting point*, not a committed action — the edited values, not the proposed ones, flow back as the action event.164- **Gated action.** A rendered control whose effect is withheld until explicit user confirmation. The click produces the structured event; the destructive/external effect is a *separate*, user-identity-gated tool call (this is the side-effect policy of the interaction loop, surfaced as a UI state).165- **Verification loop.** The model emits a result, the user reviews and accepts/rejects/annotates, and the rejection or annotation is encoded back as a turn the model reasons about — turning a one-shot render into an iterative, user-supervised refinement.166167The unifying rule across phases and HITL patterns: **a non-final state must be visibly non-final and must not be wired to side effects.** Provisional UI that looks committed, or a proposed action that fires before the user approves, is the lifecycle-level form of the same failure the interaction loop guards against at the event level.168169## Upstream Displacement Check170171Current upstream state does not make this skill obsolete. It changes which implementations are the best examples:172173- OpenAI's current API framing is Responses-API structured output / function calling plus Apps SDK widgets — not new work on the older Assistants API as the canonical path.174- MCP Apps is now a portable hosted-widget *standard* (official 2026-01-26 extension: `ui://` resources, tool linkage, sandboxed iframes, JSON-RPC/postMessage), not just a vendor-local trick.175- A2UI is a current public-preview/open-standard example of the typed-component-tree substrate (declarative JSON over trusted catalogs, JSON-Pointer data binding, native rendering).176- AG-UI is useful context for evented agent/frontend interaction and HITL flows, but it is a *transport*, not a generation mechanism and not a renderer.177- Anthropic now documents strict tool use as grammar-constrained schema adherence; non-strict tool use should not be used as the production render contract.178- Vercel's current production path is AI SDK UI generative interfaces (UIMessage/tool parts); AI SDK RSC `streamUI` remains a useful example but Vercel's own docs call RSC experimental and recommend AI SDK UI for production.179180The durable rule: prefer the strongest provider-native or strict-tool mechanism available, but keep the portable renderer contract independent of any one vendor — the pattern predates and outlives any single implementation.181182## Vendor And Framework Examples183184| Implementation | Provided by | Substrate / mechanism | Status (2026) |185|---|---|---|---|186| Structured Outputs (`text.format` / `response_format`) | OpenAI | Provider-native schema-constrained final output (grammar-constrained decoding against JSON Schema) | Current — strong for direct UI-spec generation; prefer over JSON mode |187| Responses-API function tools with `strict: true` | OpenAI | Strict JSON-Schema function-call arguments | Current — strong for component-as-tool or tool-result binding |188| OpenAI Apps SDK (`window.openai`, `apps-sdk-ui`) | OpenAI | Sandboxed-iframe components over MCP; host bridge for `toolInput`/`toolOutput`/`setWidgetState`/`callTool`/`requestDisplayMode`; inline-card/carousel/fullscreen display modes; transcript-visible data vs component-only `_meta` split | Current — still requires CSP, origin, and action policy; prefer the `ui/*` MCP-Apps bridge methods over legacy `window.openai`-only calls where both exist |189| MCP Apps (`@modelcontextprotocol/ext-apps`) | Model Context Protocol | Sandboxed-iframe HTML resource (`ui://`) + postMessage/JSON-RPC bridge; CSP/domain policy | First official MCP extension, ratified 2026-01-26; launch blog names ChatGPT, Claude, Goose, and VS Code as clients (MCP-UI adopters such as Postman/Shopify are a broader, separate set — do not conflate) |190| MCP-UI (`@mcp-ui/client`) | MCP-UI community / Shopify et al. | Three render modes: raw HTML, external URL, remote-DOM | Current — the rendering library behind many MCP Apps hosts; remote-DOM avoids iframe overhead but needs client-side component-library coordination |191| Anthropic strict tool use | Anthropic | Grammar-constrained schema adherence for tool inputs | Current — strong for schema-valid component/tool arguments |192| Anthropic fine-grained tool streaming | Anthropic | Streams tool-input deltas before buffering/validation | Current — latency feature; handle invalid/partial JSON and commit only after validation |193| Gemini response schema | Google | Response constrained to a JSON Schema subset | Current — strong for direct UI-spec generation; still validate semantic/business correctness in app code |194| A2UI | Google / A2UI project | Declarative JSON messages over a client-held component catalog, JSON-Pointer data binding, native renderer mapping | Current public-preview / open standard — first-class typed-component-tree (restriction) substrate; label maturity clearly |195| AG-UI protocol + A2UI | CopilotKit (adopted by Google, LangChain, AWS, Microsoft, Mastra, PydanticAI) | Event-based agent↔frontend *transport* (message/tool-lifecycle/state events); carries generative-UI specs including A2UI | Current — transport, distinct from the rendering substrate; visualize traces/tool events, not raw chain-of-thought |196| AI SDK UI generative user interfaces | Vercel | Tool calls/results rendered as typed React UI parts (UIMessage/tool parts) | Current — Vercel's production-oriented recommendation |197| Vercel AI SDK RSC (`streamUI`) | Vercel | RSC streaming of components selected by function calling | **Paused / experimental — not for production.** Known failure modes: stream-completion flicker/remount, multiple-suspense-boundary crashes, `createStreamableUI` quadratic data transfer; migrate to AI SDK UI (`useChat`); the older `render` API is removed |198| LangChain structured + frontend structured output | LangChain | ProviderStrategy vs ToolStrategy; validated structured response mapped to UI with progressive rendering and fallbacks | Current — portability layer; renderer still owns validation, fallback, and safety |199| Generic JSON mode / OpenRouter-compatible JSON | Multiple | Free-form JSON with post-validation, falling back to retry | Current — prototype-grade; not a production render contract by itself |200201Citing one vendor's API as the canonical pattern is a category error; the pattern predates and outlives any single implementation. The 2025–2026 center of gravity shifted toward MCP-based sandboxed-iframe apps (MCP Apps, OpenAI Apps SDK), open typed-component standards (A2UI), and event-transport protocols (AG-UI). Use whichever substrate + mechanism the chosen host and model support most reliably.202203## Verification204205After applying this skill, verify:206- [ ] The component palette is a published, typed schema (JSON Schema, TypeScript discriminated union, or equivalent) — or, in the iframe substrate, a registered set of `ui://` resources — not an informal convention.207- [ ] The palette is a discriminated union with semantic type names and bounded, shallow props; it stays within every target provider's structured-output size/depth limits, carries a version, and has a CI test asserting each provider accepts and round-trips it.208- [ ] Provider schema compatibility is tested against the actual model/API, not assumed from full JSON Schema; unsupported keywords are removed or replaced deliberately, and provider-specific subsets do not leak into a portable palette.209- [ ] Every component has an explicit fallback representation (plain-text/summary/aria) for unknown / validation-failed / newer-than-renderer specs, and for when accessibility requirements cannot be met — "unknown spec" is a designed state, not an uncaught exception reaching the user.210- [ ] The generation mechanism is provider-native structured output or strict/schema-enforced tool calling — not best-effort function calls and not free-form-with-retry in production.211- [ ] Streamed/partial specs drive only visibly-provisional UI (skeletons, placeholders); committed, interactive, or side-effecting components mount only after the full spec passes validation, and interaction is wired only post-validation.212- [ ] The render substrate is explicitly chosen (typed-component-tree vs sandboxed-iframe) and its safety basis named (restriction vs isolation).213- [ ] Every spec the model emits is parsed and validated against the schema by the render pipeline before any component is invoked — the model's emission alone does not authorize rendering.214- [ ] In the typed-component substrate, per-component safety policies are enforced: image URLs pass an origin allowlist; link targets are sanitized; content-length is bounded; nested specs terminate in finite depth.215- [ ] In the sandboxed-iframe substrate, the iframe sandbox blocks parent-DOM/cookie/storage access; the host controls the iframe's permissions and CSP/domain policy; external opens are allowlisted; tool calls the iframe requests are proxied through host authorization, not granted by the iframe.216- [ ] For hosted widgets, the tool result splits its audiences correctly: data the model must reason about is in `structuredContent`/`content`; render-only or bulk/untrusted data the model should not see (or pay tokens for) is in `_meta` (component-only).217- [ ] Raw chain-of-thought / private reasoning tokens are never piped into the UI as content; only sanitized progress labels or tool/activity traces are shown.218- [ ] Each lifecycle phase has a designed, visibly-non-final state (thinking/progress, streaming/provisional) distinct from the finalized render; the chosen HITL pattern (propose-edit, gated action, or verification loop) is explicit, and no non-final state is wired to a side effect.219- [ ] Every interactive component has a stable per-instance id, a defined action shape, a history encoding (a user-role `tool_result`/app-defined block, or a `ui/` postMessage; **not** a standard provider `ui_event` content type, which does not exist), a side-effect policy, and a state-update rule. Auto-executing destructive actions on rendered-component clicks is forbidden; capability delegation routes through the host with user consent.220- [ ] Tool-result-to-component binding is named as such when the model chose a tool rather than an arbitrary UI tree.221- [ ] The model never authors raw HTML injected into the host DOM. What it authors is a typed selection plus data — in the iframe substrate the HTML resource is developer-authored, not model-written.222- [ ] The pattern is used for open-ended assistant responses, not for known repeated workflows where traditional UI design is the better fit.223- [ ] Accessibility (keyboard navigation, ARIA roles, focus management) is the responsibility of the application's component implementations (or, in the iframe substrate, the embedded app) — generative UI does not produce accessible UI for free.224- [ ] Vendor examples in docs name their maturity: production-oriented, experimental, hosted-platform-specific, or prototype-only.225226## Do NOT Use When227228| Instead of this skill | Use | Why |229|---|---|---|230| Designing the page-level rendering model (CSR vs SSR vs RSC vs streaming) | `rendering-models` | re231232…(truncated)
Run npx skillmds@latest add jacob-balslev/generative-ui in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Use when reasoning about the pattern where a language model emits structured output describing UI components or a UI sub-tree that an application renders for the user. Covers the typed-schema component palette, JSON Schema/function-calling constraints, two render substrates (typed component tree vs sandboxed iframe), the app-side render pipeline, bidirectional interaction loop via postMessage/JSON-RPC, the security boundary between model author and application renderer, and distinctions from chat markdown, prebuilt-widget routing, RSC streaming, and model-emits-code patterns. Do NOT use for page-level rendering taxonomy (use rendering-models), the tool-call protocol cycle (use tool-call-flow), untrusted-content defenses (use prompt-injection-defense), or general component-library architecture (use design-system-architecture). Do NOT use for design the JSON shape of an HTTP API endpoint (use api-design). It is listed under Web & Frontend on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: makes network calls. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
jacob-balslev (@jacob-balslev) published this skill. Their other Agent Skills are listed on their SkillMD profile.