Georgii EmelianovEngineering

The model picks components. It never picks a colour.

Context makes off-brand output unlikely. A contract makes it unrepresentable. If the format has no colour field, the argument about tokens never has to happen.

A printer's metal type-case drawer of fixed rectangular compartments, most holding a single identical grey lead slug and a few left empty.

You can keep AI generated UI on brand for about a week with a prompt. You paste the token list into the system message, the first fifty screens come back using color.action.primary, and the team relaxes. Then one ships with a blue nobody chose, and the review afterwards cannot name the layer that was supposed to catch it. There was no such layer. There was a suggestion with a good success rate.

That is the difference between context and a contract. Context, meaning tokens in the prompt, a component registry, an indexed codebase, makes off-brand output unlikely. A contract makes it unrepresentable. This is about the second one: what the format between your server and your app has to look like so a model composing a screen at request time cannot emit a colour, because the document has nowhere to put one.

If a model composing screens rather than picking between screens a designer already built is new to you, generative UI on iOS covers that first.

The short version

Keeping generated interfaces on brand at runtime is a schema problem, not a prompting problem. Remove every visual property from the format the model emits, close every object so unknown keys are rejected, let the model choose semantic tone rather than appearance, and hold the design tokens on the client where your app already owns them.

  1. Delete visual properties from the wire format. No color, no font, no padding, no free-form node tree.
  2. Close every object. additionalProperties: false everywhere, so an invented key fails validation instead of arriving unnoticed.
  3. Give the model an enum where it wants a value. tone: "warning", never #D14.
  4. Keep the tokens on the device, in your app's own style type, and never serialize them to the side of the wire the model can see.

Why the usual way to keep AI generated UI on brand does not survive runtime

Almost everything written on this subject solves a different version of it. Vercel's AI-powered prototyping with design systems (22 August 2025) recommends publishing a shadcn/ui registry so your components reach the model "in a format models can use". LogRocket's guide to aligning AI-generated designs with your design system (20 March 2026) calls for explicit tokens, prompt templates carrying the system rules, and a review stage that treats model output like work from an outside vendor. Builder.io's Design System Intelligence docs, read on 6 August 2026, describe indexing: "The AI scans your codebase once, learns what components exist, how they work, and when to use them."

All three are correct, and all three are about design time. Each assumes two things you do not have at runtime.

They assume a reviewer. Someone opens the output, checks it against the system, and rejects it. That person is the enforcement. Where the screen is composed while a customer waits for it, there is no queue and no reviewer, and whatever the model emitted is what somebody sees.

They assume a build step. Indexing, registries and rules files shape code before it ships. A generated screen has no build. It goes from bytes on a wire to pixels in one hop, with no compiler and no pull request in between.

Take those away and every method on the list degrades to the same thing: a persuasive request. Models comply with persuasive requests most of the time, which is a fine bar for a prototype and the wrong bar for a screen your brand team signed off on.

Constraint beats context: take style out of the contract

The alternative is to stop asking. If the format the model emits has no field for a colour, no off-brand colour can be emitted, and no validator has to be smart enough to recognise one. The failure mode is not caught. It does not exist.

That means a fixed catalog of records rather than a node tree. A node tree is open by construction: any element can carry any attribute, so the moment you accept one you are back to inspecting attributes. A catalog is closed. Here is a real definition from a shipped wire schema, unabridged:

{
  "type": "object",
  "additionalProperties": false,
  "required": ["type", "tone", "body"],
  "properties": {
    "type": { "const": "callout" },
    "tone": { "enum": ["neutral", "information", "success", "warning"] },
    "title": { "type": "string", "minLength": 1, "maxLength": 120 },
    "body": { "type": "string", "minLength": 1, "maxLength": 600 }
  }
}

Four properties. Two of them are strings the model writes, one is a constant, one is a four-value enum. There is no fifth property, and additionalProperties: false means there cannot be: a record carrying "backgroundColor": "#4A90D9" is not an off-brand record, it is an invalid one, and it fails on the same code path as a malformed date.

A design system defended by review is a design system defended by attention. A design system defended by a closed schema is defended by the parser.

What the model emits instead: meaning, not appearance

The model still has something real to say about emphasis. A late-payment notice is not a shipping confirmation, and an interface that renders them identically is worse, not safer. The trick is that the model says which one it is and never what that looks like.

In the schema above, that is tone. The model picks warning; the client decides whether warning is amber, a left rule, a heavier weight, or all three. The same split runs through the catalog: a metric item takes an optional tone of neutral, positive or negative, so the model can mark a number as bad news without knowing your palette has a red in it.

Two consequences worth having:

  • A rebrand is a client release. Change the token values in the app, ship it, and every screen the model has composed or will compose follows. Nothing on the server changes, because the server never knew the colours.
  • Dark mode is free and correct. Semantic tone resolves per appearance on the device. A model emitting hex would have to be told which mode the user is in, and that is one more thing it can be wrong about.

Cardinality is a brand rule too

Colour is the obvious half. Density is the half that gets missed, and it belongs to the system too: your grid was designed for four tiles, not nine, and a screen that scrolls for a minute is off brand even when every pixel is a token.

Bounds belong in the same contract. In the schema quoted above, a metrics record takes minItems: 2 and maxItems: 4; a narrative record takes at most three paragraphs of 800 characters and six bullets; a callout body stops at 600 characters. The nine-tile wall is not a design review finding. The tenth item fails validation before the screen exists.

Two details make that hold. The limits sit in the schema rather than the prompt, so they are checked and not requested. And the client re-implements them: the iOS reducer applies its own cardinality limits to the stream it receives, and the tighter of the two wins. A server that gets more permissive by accident cannot quietly widen what your app draws.

What this still does not protect you from

This buys you visual safety, and visual safety only. Be clear about the rest.

  • The words are still the model's. A schema polices shape, not copy. Tone of voice, claims, legal phrasing and translation quality are all prompt and evaluation problems, and they stay yours.
  • The wrong record is still on brand. A comparison where a three-step plan was the right answer renders perfectly and helps nobody. Choosing the record type is judgment, and judgment is not schema-enforceable.
  • Semantic tone can be misapplied. success on bad news paints the interface in your success colour, correctly and wrongly at the same time.
  • Contrast and accessibility are yours. The client owns the tokens, so the client owns the contrast ratio, the dynamic type behaviour and the reduced-motion path.
  • The trade only works if a catalog can carry your answers. If every answer needs bespoke layout, a closed catalog is the wrong instrument and you should keep the design-time tooling and the reviewer. Ask whether your app's answers fall into a small number of recurring shapes. If they do, you are giving up range you were never going to use.

How Uzori does it

Uzori's wire contract is a fixed catalog of 23 record types. A check of the schema file on 6 August 2026 returns zero occurrences of color, font, spacing, padding, theme or any other appearance key, and there is no node tree. additionalProperties: false appears 28 times in that one file, once per definition.

The input side is closed too. The presentation stage's turn input has exactly six top-level properties, and none of them is a theme:

{
  "additionalProperties": false,
  "required": [
    "protocolVersion",
    "runtime",
    "request",
    "context",
    "groundedData",
    "allowedInteractions"
  ]
}

Style lives on the device, in a Swift value type the host app configures: background, content background, colour scheme, layout, chrome, component and content tokens. It is never serialized to the server, so it is not in the prompt and not in any log the model reads.

The same posture covers everything else the model might reach for. Every emitted line is validated before it reaches the device, and the gate forwards the original bytes, so what renders is byte-identical to what passed. An invalid line is rejected rather than patched; one bounded re-ask is permitted only when the first record fails and nothing has been forwarded. Actions are host-owned: the wire accepts only an actionRef, so the model can point at a button you declared and cannot invent one. Rendering is native SwiftUI, with no WebView in the SDK. The rest of this series on what the model cannot do takes those one at a time.

Frequently asked questions

Can AI use my existing design system?

At design time, yes, and that is what most tooling is built for: index the components, publish a token registry, and the model generates code against them. At runtime the question inverts. Instead of teaching the model your system, you remove appearance from what it can emit and let the client apply the system. The model composes structure; your code owns every visual decision.

Are design tokens as guardrails enough on their own?

Tokens in a prompt reduce the error rate; they do not bound it. A token list is context, and a model can always emit a plausible value that is not on the list, especially on a long or unusual request. Tokens become an actual guardrail only when the format has no place to put anything else, so the model's choice is between a valid record and a rejected one.

How do I stop an LLM generating off-brand UI at runtime?

Remove the fields. Give the model a closed catalog of record types with additionalProperties: false, no colour, font or spacing properties, and enumerated semantic tone instead of visual values. Validate every record against that schema before it reaches the device, and reject anything that fails rather than repairing it. What survives is on brand by construction, not by inspection.

Do I have to review every AI-generated screen?

Not for visual conformance, if the contract has no visual fields to get wrong. You do still review the things a schema cannot see: whether the copy is accurate, whether the chosen record type answers the question, and whether the data behind it is grounded. Move the review effort from checking pixels, which a parser can do, to checking meaning, which it cannot.

Where to start

Open whatever format your generated screens already travel in and run three checks.

  1. Grep it for appearance. Search the schema for color, font, size, padding, style and class. Every hit is a decision you have delegated to a model.
  2. Count your open objects. Any definition without additionalProperties: false accepts keys you never designed, and your renderer ignores them right up until someone ships a renderer that does not.
  3. Find where your tokens live. If they sit on the server side of the wire, they are reachable by the prompt. Move them into the client.

For the version of this that ships as a product, with the schema, the validation gate and the native renderer in place, Uzori is the in-app assistant built on exactly this trade.

← All posts