A trustworthy generative UI does not ask an LLM to emit arbitrary interface code. It lets the model select a bounded tool or presentation intent, validates the returned data against a schema, and maps that result to tested components such as a chart, table, metric, comparison, or action. Text remains the explanation; structured UI makes evidence inspectable.

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#

Evidence trailSource 2
  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.

Copyable contract

Make every rendered view explain its state and provenance#

Evidence trailSource 2

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.

View selection

Choose a representation by question, not novelty#

Evidence trailSource 1Source 2

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.

Sanitized product pattern

Conversation becomes a control surface for analysis#

Evidence trailSource 1

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.

Operational states

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

Evidence trailSource 1Source 3

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.

Action safety

A generated control still needs ordinary authorization#

Evidence trailSource 2Source 4

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.

Accessibility

Every visualization needs a non-visual path#

Evidence trailSource 3
  • 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.

Boundary

What generative UI should not generate#

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

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.