Letting a model invent interface code can turn a presentation choice into an unsafe action. A trustworthy generative user interface (UI) limits the model to approved views, validates the returned data, and renders tested components such as a chart or table. Text explains the result; the structured view makes its evidence easier to inspect.

Why this matters

The happy-path card worked; the interface failed everywhere else#

A model produces a convincing account-summary card in a demo. In production, an unknown field breaks rendering, a partial tool result leaves a blank region, keyboard focus disappears after regeneration, and an unauthorized action is displayed as if it were available. The generated answer looked right; the product states were not controlled.

Generative UI is therefore a component-boundary problem before it is a prompting problem. The model may choose among bounded intents, but schemas, tested components and application permissions must own what renders, what can be done and how every incomplete or failed state behaves. It must not own the document object model (DOM), the browser structure of the page.

The broken states point to one design boundary: the model may choose an intent, while tested components keep execution.

Decide

How model output becomes trusted interface state

Artifact

A typed allowlist renderer and action boundary

Prerequisite

A tool result schema and tested components

Trusted rendering chain
  1. 01
    IntentAllowed tool or view
  2. 02
    SchemaValidated result
  3. 03
    ComponentTested rendering
  4. 04
    StatesLoading · empty · partial · error
The model selects an allowed intent; schemas and tested components own execution. Every result also has explicit loading, empty, partial, and error states.

Rendering contract

Separate model choice from component execution#

The four layers below separate a model suggestion from what the application is allowed to render and execute.

  1. 01

    Interpret

    The model identifies the user goal and selects an allowed tool or view intent.

  2. 02

    Validate

    A schema constrains fields, types, ranges, labels, and empty states.

  3. 03

    Render

    A trusted component receives validated data and owns accessibility and interaction.

  4. 04

    Explain

    Text states what the view shows, its source, and important uncertainty.

Evidence trailSource 2

With ownership separated, the payload must carry enough context for the component to explain what the reader sees.

Copyable contract

Make every rendered view explain its state and provenance#

A small envelope keeps presentation intent separate from business data. The server validates it before the client selects a component; unknown component names, invalid fields, and unsupported actions fail closed.

The example is deliberately generic. In production, version the schema and authorize every action on the server rather than trusting values proposed by the model.

Reference implementation

Let the model choose intent, not executable UI

This sanitized contract reflects a production conversational UI pattern: validate data, map only known variants, and authorize side effects outside the model response.

trusted-renderer.ts
type ViewSpec =
  | { schema_version: "1"; kind: "metric"; label: string; value: number }
  | { schema_version: "1"; kind: "comparison"; rows: Array<{ label: string; value: number }> };

type RenderNode = { component: "MetricCard" | "ComparisonTable"; props: object };

function parseView(input: unknown): ViewSpec | null {
  if (!input || typeof input !== "object") return null;
  const value = input as Record<string, unknown>;
  if (value.schema_version !== "1") return null;
  if (value.kind === "metric" && typeof value.label === "string" && typeof value.value === "number") {
    return value as ViewSpec;
  }
  if (value.kind === "comparison" && Array.isArray(value.rows)) {
    const valid = value.rows.every(row => row && typeof row.label === "string" && typeof row.value === "number");
    return valid ? value as ViewSpec : null;
  }
  return null;
}

const registry = {
  metric: (view: Extract<ViewSpec, { kind: "metric" }>): RenderNode =>
    ({ component: "MetricCard", props: { label: view.label, value: view.value } }),
  comparison: (view: Extract<ViewSpec, { kind: "comparison" }>): RenderNode =>
    ({ component: "ComparisonTable", props: { rows: view.rows } }),
};

export function renderToolResult(input: unknown): RenderNode | { component: "SafeFallback"; props: object } {
  const view = parseView(input);
  if (!view) return { component: "SafeFallback", props: { reason: "invalid_view_contract" } };
  return view.kind === "metric" ? registry.metric(view) : registry.comparison(view);
}

export function authorizeAction(input: { action: string; requires_confirmation: boolean }, confirmed: boolean) {
  if (input.requires_confirmation && !confirmed) return { status: "confirmation_required" };
  return { status: "authorized_for_server_validation", action: input.action };
}

console.log(JSON.stringify([
  renderToolResult({ schema_version: "1", kind: "metric", label: "Conversion", value: 12.4 }),
  renderToolResult({ schema_version: "2", kind: "html", value: "<script>" }),
  authorizeAction({ action: "publish_report", requires_confirmation: true }, false),
], null, 2));
Runtsc trusted-renderer.ts && node trusted-renderer.js
Expected output
[
  {
    "component": "MetricCard",
    "props": {
      "label": "Conversion",
      "value": 12.4
    }
  },
  {
    "component": "SafeFallback",
    "props": {
      "reason": "invalid_view_contract"
    }
  },
  {
    "status": "confirmation_required"
  }
]

Failure exercised. The invalid schema becomes SafeFallback; the sensitive action remains blocked until confirmation.

Production boundary. A framework adapter may render these nodes in React, Vue, or native UI. The trust boundary stays framework-independent.

Evidence trailSource 2

A valid payload still needs the right representation for the user’s question.

View selection

Choose a representation by question, not novelty#

Use a metric for one important value, a table for precise lookup, a line for change over time, bars for category comparison, and text when the evidence does not justify a visualization.

Give the model a small catalog with descriptions and constraints. If no component fits, fall back to text or a table rather than generating an unsupported chart.

Evidence trailSource 1Source 2

The account-summary case shows how that choice works when conversation becomes a control surface.

Sanitized product pattern

Conversation becomes a control surface for analysis#

In a conversational analytics product, the user does not only ask for an answer. They refine a segment, compare alternatives, inspect evidence and continue from the current state.

The UI therefore needs stable identifiers, provenance and reversible actions. A follow-up such as “compare only the top two” should transform validated data, not reinterpret a screenshot or prose paragraph.

Evidence trailSource 1

Once the useful view is defined, incomplete and failed states need the same design attention as success.

Operational states

Loading, empty, partial, stale, and error are first-class#

Tool calls take time and can fail independently. Components need explicit states and accessible status messages, while the conversation needs a recovery path that preserves the user’s intent.

Do not render a confident empty chart when data is missing. Show the boundary: no data, filtered out, unavailable source, or tool failure are different states.

Evidence trailSource 1Source 3

Visible state is not authorization: every generated action must pass ordinary application controls.

Action safety

A generated control still needs ordinary authorization#

Separate read-only exploration from state-changing actions. A button proposed by the model is not permission: the server must verify identity, scope, current state, and the exact operation before executing it.

Use stable action identifiers, idempotency for retries, confirmation for destructive or expensive changes, and a visible result. When authorization or evidence is missing, preserve the analysis and disable the action instead of guessing.

Evidence trailSource 2Source 4

A safe action can still exclude users, so the interface also needs a complete non-visual path.

Accessibility

Every visualization needs a non-visual path#

Check these paths for every model-selectable component, including loading, partial, and error states.

  • Provide a text summary of the main finding.
  • Offer a table or equivalent data representation.
  • Do not encode meaning only through color.
  • Announce loading and result status programmatically.
  • Keep keyboard focus predictable after updates.
  • Test reflow and zoom on small viewports.
  • Expose source, timestamp, filters, and uncertainty.
  • Keep actions reversible or confirm destructive changes.
Evidence trailSource 3

Together, state, authorization, and accessibility define what the model must never generate freely.

Boundary

What generative UI should not generate#

The boundary protects the same account-summary card from the opening: the model chooses among tested options; it does not invent product behavior.

CAN

  • Select from tested components
  • Adapt presentation to validated data
  • Expose evidence progressively
  • Support follow-up analysis

CANNOT

  • Execute arbitrary model-authored UI code
  • Make unsupported data trustworthy
  • Replace accessibility with a chart
  • Hide uncertainty behind polish

The resulting rule is simple: generate bounded choices, then let ordinary product code enforce them.

Conclusion

Generate choices, not unchecked interface code#

A trustworthy generative interface does not give the model ownership of the DOM. The model selects a bounded intent; schemas validate the result; tested components own rendering, accessibility, permissions, and the full set of loading, empty, partial, success, and error states.

Begin with one useful view and a short allowlist of actions. Make every state reproducible without the model, reject unknown payloads, and log the selected intent rather than sensitive conversation content. If safe rendering still depends on accepting arbitrary markup, the component boundary is not finished.

Read next

Primary sources

Primary sources

  1. AI SDK: Generative User Interfaces

    Tool results mapped to interface components.

  2. JSON Schema basics

    Constraints for structured data.

  3. Web Content Accessibility Guidelines 2.2

    Text alternatives, reflow, semantics, and status messages.

  4. OWASP Top 10 for LLM Applications 2025

    Excessive agency, insecure output handling, and sensitive information disclosure.

Design beyond the chat bubble

Build conversational products that turn evidence into usable interfaces.