Generating UI with Structured Outputs

Structured Outputs

by Dexter Storey, Sarim Malik

September 12, 2025

Generating UI with Structured Outputs

TLDR: Generate UI from schema-validated JSON, not code. It's fast, safe, and production-ready. Try it in our playground.

It's easy to start building AI-generated UI, but hard to ship.

Here's the core problem: how do we reliably go from natural language to code to a working UI? And even if the UI is functional, how do we make it pleasant to use?

If you ask a model to write UI code, you often end up in purple hell. It all looks the same and you immediately know it's AI generated. Simon Willison would describe this as "slop".

[image]: a generic prompt and the generated UI

Even with strong primitives like shadcn/ui, code generation can improve how it looks, but it is still brittle and tied to your environment. Generating code on the fly is not viable for end users. It is slow: write, build, bundle. It is also failure prone: type errors, missing imports, and runtime mismatches.

A better approach is to generate JSON, not code.

LLMs are good at JSON when you give them a schema. You can validate it, render it, and recover when something goes wrong.

Everything is Schema

How It Works (Step by Step)

Define your components with Zod

  • Give every UI primitive its own props schema.
  • Combine them into a master ComponentsSchema that describes valid UI trees.
  • Add variables, queries, and repeat so the UI can bind data and render lists.

Prepare the schema for the model

  • Turn your Zod schema into JSON Schema with z.toJSONSchema(..., { metadata: SchemaRegistry }).
  • Optionally limit which components are allowed and set sensible defaults.

Ask the model for structured output

  • Send a simple system prompt that explains the job: “turn requests into a UI.”
  • Include the current UI state so the model can update in place.
  • Request output that matches your JSON Schema.

Validate the result

  • Parse the model’s JSON and run ComponentsSchema.safeParse(...).
  • If it fails validation, return the issues and do not render.

Render the UI

  • Map the validated JSON tree to your React components at runtime.
  • No code generation. No build step. It renders immediately.

Iterate safely

  • For each edit, send the current state and the user’s change.
  • The model updates the existing tree instead of starting from scratch.

[image] Data flow at a glance: User prompt → API route → Model with JSON Schema → JSON output → Zod validation → UI render.

Lessons from Scaling

Why This Matters