A guide to trustworthy generative UI
From the basics of generative interfaces to how Catalystic UI makes them safe enough to ship.
Overview
Catalystic UI turns your design system into a catalog that lets an AI agent compose interfaces which look like your product, and, crucially, can be trusted with the parts that must never break, like payment and confirmation.
Instead of returning markdown or raw HTML, an agent returns a small JSON component tree. Catalystic UI validates that tree against the catalog and renders it with real components in the design system you choose, shadcn, Polaris, Material, Carbon, or your own imported from Figma.
Interfaces that build themselves
For decades, interfaces were fixed. Developers wrote every screen ahead of time and the app showed the right one at the right moment. A menu was a menu; a checkout was a checkout. Nothing was decided at runtime.
Generative UI breaks that assumption. Instead of only returning text, an agent returns interface, cards, forms, charts, buttons, assembled on the fly from what the user actually asked for. Say "show me chicken dishes under £10" and the agent composes exactly that view rather than routing you to a prebuilt page.
The problem nobody demos: trust
Generative UI demos beautifully and ships almost nowhere. A model is powerful because it's nondeterministic, it improvises. That's wonderful for knowledge, but a UI is something you act through, and hallucination is not a feature there. No bank, no store, no serious product team will let a language model freestyle a payment screen: one reworded "Confirm," one hallucinated field, one off brand component isn't a quirk, it's an incident.
So the hard problem in generative UI was never generating UI. Everyone can do that now. The hard problem is generating UI you can trust, UI that is on brand, valid, and, where it matters, exactly the same every time. That is the problem Catalystic UI is built to solve.
A2UI, the standard we build on
Rather than invent a proprietary format, Catalystic UI builds on A2UI, an open, declarative protocol for agent composed interfaces (the A2UI basic catalog format). An agent does not emit HTML/CSS/JS, it emits a compact JSON payload that references preapproved components. The host renders those components natively, in its own design system.
That indirection is what makes A2UI safe and portable:
- Native rendering, the same payload renders in React, Flutter, or your own renderer, always in your brand.
- Capability based security, the client only renders components from a known catalog; it can't be tricked into running arbitrary markup.
- Efficiency, referencing components by name is far cheaper (in tokens) than regenerating markup every turn.
But A2UI has one dependency that decides whether the whole thing works: the catalog. A catalog defines, for every component, its schemas (name, props, variant enums, required fields) and its guidance (prose telling the agent when to use it and which variant fits, plus hard rules). A weak catalog produces weak output. Catalystic UI's entire job is to produce a great catalog from your design system.
The core idea: split the mechanical from the judgement
A catalog has two kinds of content, and they should never be produced the same way.
| Layer | What it is | Who writes it |
|---|---|---|
| Schemas | Component names, props, variant enums, required fields | Code, extracted from the design system, never guessed |
| Guidance | Prose telling the agent when/why to use a component, plus hard rules | The model, this is judgement |
If your design system says a Button's variant is one of {primary, secondary, ghost, destructive}, that's a fact, asking a model to reproduce it only introduces drift, so it's extracted by code. But there's no "correct" sentence explaining when to use a ConfirmSheet, only a well judged one, so that half is generated by the model.
Let the model be creative where creativity is safe. Pin down everything that has a correct answer, and everything that must never break.
Architecture at a glance
Catalystic UI has two halves: a build time pipeline that produces the catalog, and a runtime that renders and verifies agent output against it.
The pipeline runs occasionally (when the design system changes). The runtime runs on every interaction. Determinism lives in different places in each, and getting that right is the whole game.
The build time pipeline Shipped
The pipeline takes an intermediate representation of your design system (a Figma REST extraction or a Storybook scrape) and produces an A2UI catalog. It alternates deterministic code and model judgement.
Stage 1, Extract schemas (deterministic, no model). Code reads each component and builds its schema: props, variant enums, required fields. Transient render states (hover, focus, open) are dropped, an agent sets semantic variants, not render states.
# variant properties become enums, straight from extraction, never hallucinated
for vprop, values in comp.get("variantProperties", {}).items():
props[vprop] = {"type": "string", "enum": values}
Stage 2, Write guidance (model). For each component the model fills in the description fields: when to use it, which variant fits, common mistakes. Structured output guarantees the JSON shape.
Stage 3, Write rules (model). The model produces a short rules.txt of hard, nonnegotiable MUST rules (e.g. "For Button, you MUST provide action").
Stage 4, Validate (deterministic). Code checks the two halves agree: every prop a rule or description leans on must actually exist in the extracted schema. Judgement can't invent a field ground truth doesn't have.
Stage 5, Assemble. The schemas are assembled into the A2UI catalog shape, each component emitted with unevaluatedProperties: false, strict enums, and const names. That makes catalog.json a strict, machine checkable contract, not just documentation.
Consequential components
One input field matters more than the rest: a consequentialHint that marks components whose action or content must not be recomposed or reworded, Button, AmountInput, ConfirmSheet, anything in a pay/transfer/confirm flow. Catalystic UI carries this flag through the pipeline (surfaced as x-consequential in the catalog and mirrored in consequential.json). Stage 2 turns it into guidance, Stage 3 into a rule. This is the hook that lets the runtime enforce trust exactly where it's expected.
Determinism, precisely: build vs run
There are two kinds of determinism you want, solved differently:
- Validity determinism, the output is always conformant: only real components, valid props, valid variants; consequential components never reworded. It may vary, but it's guaranteed correct and safe.
- Identity determinism, the same intent + data produces the same interface every time. This is what makes caching and reproducibility possible.
Catalystic UI gets validity determinism at build time for free: catalog.json is a strict schema. But a schema in a file guarantees nothing at runtime, the agent still emits a surface on every turn, and something has to enforce the contract before that surface renders. That something is the verifier.
The runtime: render natively, verify strictly Shipped
At runtime the agent proposes an A2UI surface. Before it reaches the screen it passes through one gate, implemented in both the browser (verify.js, AJV) and on the server (verify.py, jsonschema), so client and server agree on what's valid.
The schema gate. The surface is validated against catalog.json. Because the catalog uses unevaluatedProperties: false plus enums, this automatically rejects unknown components, invalid variants, hallucinated props, and missing required fields, no per case logic needed.
Failure policy, split by consequence, the determinism thesis made real:
- A nonconsequential node fails → repair: drop the unknown prop, snap a bad variant to its default, or ask again with the validation error. Tolerance is fine here.
- A consequential node fails → reject. Do not render. No silent coercion, no guessing. This is where determinism must be absolute.
Rendering in your brand. The renderer maps verified nodes to native, design system styled components. Because the agent only ever emitted intent (not markup), the output comes out on brand automatically, a "flight card" or a "metric" looks like your product because your renderer decides how it looks, not the model.
The renderer runs strict by default: it assumes verified input and renders literally. A LENIENT flag turns on the forgiving behaviours (remap hallucinated variants, tolerate malformed text) for the demo playground. Forgiveness and determinism are opposites; you choose per surface.
id rather than compose them, and asserting the emitted node matches the pinned one byte for byte. Today the reject path is enforced; canonical pin by id is the next increment.Fast by default: what should not touch the model
A recurring mistake in generative UI is routing everything through the model, which makes apps slow and expensive. The rule of thumb:
- Deterministic actions (add to cart, select, checkout, tap Pay) → handled in code. Zero model calls.
- Filtering, sorting, toggling → client side reactive state; the renderer reads bound values and repaints instantly, no round trip.
- Open ended composition ("build me a view of X") → the model, once, then cached (identical requests cost 0 tokens).
Most turns in a real app are the first two. Keeping them off the model is the difference between an interface that feels generative and one that feels slow.
Extending Catalystic UI: capabilities Roadmap
Real users won't hand wire tools, so capabilities are planned as self describing bundles, each a plugin delivered over MCP. A capability ships its tools (e.g. search_flights), an A2UI catalog fragment, its pinned consequential components, and a declared auth requirement.
Enabling one is a single action: the user clicks Enable, connects credentials, and the fragment is merged into their catalog and checked by the verifier. Because a capability ships intent, not styling, enabling "Flights" makes the flight cards render on brand in the user's own design system automatically, a property no styled component plugin system can match.
The A2UI schema
A surface is an array of component nodes. Each node has a type from the catalog and typed props; children may be nested inline.
{
"type": "Card",
"props": { "title": "Book an appointment" },
"children": [
{ "type": "TextInput", "props": { "label": "Name" } },
{ "type": "Button", "props": { "label": "Confirm", "variant": "primary" } }
]
}
Consequential components (Button, AmountInput, ConfirmSheet) validate strictly: if they don't conform, they're rejected rather than repaired.
Generate API
Authenticate with an API key (ck_…) in the Authorization header. Each real generation spends one credit; identical requests are served from cache for free.
# POST a prompt, get back a verified component tree
curl https://playground-eta-eight.vercel.app/api/chat \
-H "Authorization: Bearer ck_your_key" \
-H "Content-Type: application/json" \
-d '{"prompt": "a booking screen for a hair salon"}'
| Field | Type | Notes |
|---|---|---|
prompt | string | A single user message. Use history for multi turn. |
history | array | Chat messages {role, content}. |
components | array | The current surface, for follow up edits. |
mode | string | chat (default) or an agent mode. |
webSearch | bool | Let the agent pull live info. |
Responses include reply, components, usage, and credits_remaining.
Bring your own agent
Point Catalystic UI at your own agent endpoint and it drives the renderer. Your endpoint receives the chat turn and returns { reply, components }. Requests are proxied over HTTPS only, with private/loopback hosts blocked (SSRF guarded).
POST /api/proxy
{ "endpoint": "https://your-api/agent",
"body": { "history": [...], "mode": "chat" } }
Credits & billing
New accounts start with free credits. One credit is one fast generation; a higher quality generation costs five; cached (identical) renders are free. Top up any time from Settings → Usage & credits, checkout is handled by Stripe and credits are applied to your account the moment payment confirms.
Status & roadmap
Being honest about what's built and what's next:
Shipped
- Build time pipeline, extraction → guidance → validation → strict A2UI catalog (
catalog.json+rules.txt+theme.json). - Runtime verifier, the schema gate + consequential reject policy, enforced in the browser (AJV) and on the server (jsonschema), with a passing parity test suite.
- Strict renderer, strict by default; lenient fallbacks gated behind a flag.
- Consequential carried end to end,
x-consequentialin the catalog drives the reject path at runtime. - Live playground, design system switching, Figma / design token import, output caching, credits & billing, bring your own agent.
Next
- Consequential canonical match, pin pay/confirm components by
idand assert an exact match, not just schema validity. - Capability bundles over MCP, the one click enable model.
- Real design system ingestion at scale, beyond the sample catalog.
The one idea to remember
Everyone can make an AI generate an interface. The interesting, valuable, hard thing is making one you can trust, on brand, valid, and, where it counts, identical every time.
Generate freely where it's safe. Pin and verify everything that has a right answer, and everything that must never break. That's the difference between a generative UI that demos well and one that ships.