All Resources

R-35

Technology

AI2UI: Generating Interfaces That Are Still Yours

Letting a model assemble the interface without letting it invent one — a constrained component registry, schema-validated intent, and the deterministic renderer in between.

PAR2 Labs

August 21, 2026

14 min

AI2UI: Generating Interfaces That Are Still Yours

There are two ways to have a model build interface. One is to ask it for code and run what comes back, which is fast to demo and impossible to own. The other is to let it choose from components you already wrote, in a shape you already validate, and render the result yourself. Only the second survives a design system, an accessibility audit, or a security review.

01

Generate intent, not markup

The central move is to narrow what the model is allowed to produce. It does not emit JSX, HTML or CSS. It emits a small JSON document naming components from a registry you control, with props that a schema validates. Everything visual stays in your codebase.

That constraint is what makes the output boring in the best sense: it inherits your tokens, your spacing, your focus rings and your keyboard behaviour, because it is your components all the way down.

AI2UI — THE ONLY PATH FROM PROMPT TO PIXELS01Intentuser + context02Modeltool call03Schemavalidate04Registryresolve05Renderyour components06Telemetrywhat was usedNO STEP EVER PRODUCES MARKUP OR STYLES — THE MODEL ONLY EVER NAMES THINGS YOU ALREADY BUILT

Fig 1 — validation sits between the model and the renderer, and it is not optional. Anything that fails the schema is discarded and re-requested, never rendered.

02

The registry is the contract

A component registry is a whitelist with types. Each entry names a component, describes when to use it in language a model can act on, and declares its props as a schema. The registry is what you hand the model as tool definitions, and what you validate against on the way back.

Keep it small. Twelve well-described components produce better interfaces than sixty vague ones, because selection accuracy falls off as the option space grows and the descriptions blur together.

Registry fieldPurposeFailure if omitted
nameStable identifier the model selects byModel invents component names that resolve to nothing
descriptionWhen to use it, in decision languagePlausible but wrong component choice — a table where a chart was needed
propsSchemaZod/JSON Schema for every propUnvalidated props reach render and crash on the client
slotsWhich children are permittedArbitrary nesting, unbounded depth, layout that breaks
dataBindingWhich server fields may populate itModel fabricates values instead of binding real data
maxInstancesCap per responseTwelve charts where one was wanted

The registry is the security boundary as well as the design boundary — treat additions to it as you would a new public API.

A model that cannot express an invalid interface will not produce one.

03

Building it

This is the whole implementation path. It is less code than it sounds; the discipline is in refusing to widen the interface when a case does not fit.

01

Define the component registry

  • TOOL

    Zod + your design system

  • USE

    One entry per component, with description and props schema

  • GET

    A typed, enumerable registry

Start from components that already exist and are already accessible — shadcn/ui or Radix primitives wrapped in your tokens. Write each description as a decision rule: 'use when comparing a small number of categorical values', not 'a bar chart'.

Why: The model chooses by description. Descriptions written for humans read fine and select badly; descriptions written as decision rules select correctly.

02

Expose the registry as tools, not as prose

  • TOOL

    Structured tool use

  • USE

    One tool per component, or one emit_ui tool with a discriminated union

  • GET

    Model output constrained at generation time

Give the model the registry as tool definitions with JSON Schema, so the constraint is applied while it generates rather than checked afterwards. Prefer a discriminated union keyed on component name over a free-form object.

Why: Constraining at generation is far more reliable than validating after. A model that cannot express an invalid shape does not produce one.

03

Validate, then resolve

  • TOOL

    Zod parse + registry lookup

  • USE

    Reject unknown components, coerce nothing silently

  • GET

    A safe render tree

Parse the returned tree. Reject any unknown component name, any prop that fails its schema, and any nesting deeper than your slot rules allow. On failure, re-request once with the validation error included, then fall back to a static layout.

Why: This is the step people skip, and it is the one that decides whether a bad generation is a wrong-looking panel or a client-side crash in front of a customer.

04

Bind data server-side

  • TOOL

    Your existing API layer

  • USE

    Model names a data source; the server fetches it

  • GET

    Real values, correct permissions

Let the model reference a data binding by key — 'revenue_by_region' — and resolve that server-side against the caller's permissions. Never let generated output carry the values themselves.

Why: A model that supplies values will confidently supply plausible wrong ones. Resolving server-side also means row-level permissions are enforced by the same code path as the rest of the product.

05

Render deterministically

  • TOOL

    React

  • USE

    Registry map from name to component, keyed by a stable id

  • GET

    An interface indistinguishable from hand-built

Walk the validated tree and render each node from the registry map. Give nodes stable ids so React reconciles rather than remounting between generations. No dangerouslySetInnerHTML, no dynamic imports by string, no eval.

Why: Deterministic rendering is what makes the output debuggable — the same tree always produces the same interface, and a bug is in your component rather than in a string somewhere.

06

Instrument what gets chosen

  • TOOL

    OpenTelemetry

  • USE

    Log component selections, validation failures, fallbacks

  • GET

    Evidence for the next registry revision

Record which components were selected for which intents, which validations failed and why, and how often the fallback layout was used. Review weekly and either fix the description or remove the component.

Why: Selection quality is a property of your descriptions, and descriptions can only be improved from data. A component that is never chosen, or always chosen wrongly, is a registry bug.

04

What this deliberately gives up

This approach cannot produce an interface you have not already imagined. That is the trade: novelty for ownership. If you need genuine novelty, generate a candidate in a sandbox and have a human promote it into the registry — do not widen the runtime.

Non-negotiables

01

Never render model-produced markup, styles, or code. The registry is the only surface.

02

Validate before render, always, and have a static fallback for when validation fails twice.

03

Resolve data server-side against the caller's permissions — generated output never carries values.

04

Keep the registry small and its descriptions written as decision rules.

05

Accessibility lives in your components, not in the generation. If a component is not keyboard-navigable, the generated interface is not either.

05

Reference

The tool-use and JSON Schema documentation is the load-bearing reading here; the component libraries are interchangeable as long as they are accessible by default.

Primary documentation

Tool use and structured output

Constraining generation with schemas rather than validating after the fact.

JSON Schema

The vocabulary your registry compiles to.

Zod

Runtime parsing of the returned tree, with inferred types for the renderer.

shadcn/ui

Components you own outright — the right starting point for a registry.

Radix Primitives

Accessible behaviour underneath, so generated interfaces inherit it.

React

Reconciliation and keys — why stable ids matter between generations.

OpenTelemetry

Instrumenting selection quality and validation failures.

Key Takeaways

01

The model emits intent against a registry — never markup, styles or code.

02

Constrain at generation with tool schemas; validating afterwards is strictly weaker.

03

Resolve data server-side against caller permissions, so generated output carries no values.

04

Selection quality is a property of your descriptions — instrument it and revise them.


PAR2 Labs · Technology

Work With Us

Have a problem worth solving?