# Generative UI

How an agent renders typed, interactive cards in the chat — the Card Contract, how to make your model emit cards, and how to route the results back.

Your model doesn't have to answer in prose. It can ask the chat to render a **typed, interactive card** — a confirmation to approve, a form to fill, a plan to pick from, a link to preview — and get the user's answer back as structured data. The kit handles rendering and event routing; you decide when a card beats a sentence.

This guide is the model/server side: the contract, how to get your model to emit cards, and how to feed the result back. For the rendering pattern see [Generative UI cards](/patterns/generative-ui-cards/); for each card's props, the component pages ([confirm](/components/confirm/), [choice](/components/choice/), [form](/components/form/), [tasks](/components/tasks/)).

If you are wiring a model rather than reading the contract, go straight to [Schemas as tool definitions](/guides/schemas-as-tools/): `cardTools` projects the schemas below into tool definitions and `cardFromToolCall` turns the model's call back into an envelope, so none of the mapping on this page has to be written by hand.

## The loop

```
agent / server ──CardEnvelope(s)──▶  <kai-cards>.cards
     ▲                                     │  dispatcher renders the kai-* for each `type`
     │                                     ▼
     └──── result ◀── CardPolicy ◀── kai-card event ◀── user interacts
```

One **envelope** is what the agent *sends* (addressed data, not UI); one **card** is what the user *sees*. The dispatcher's whole job is envelope in → card out.

## The Card Contract

Everything the agent sends is a `CardEnvelope`:

```ts
interface CardEnvelope {
  type: string;   // which card — confirm | choice | form | tasks | link | embed | artifact
  id: string;     // stable id; every event correlates back to it
  data: unknown;  // conforms to that type's JSON Schema
  title?: string; // optional card-chrome heading
  resolution?: CardResolution; // set once resolved → renders the read-only view
}
```

The built-in types and the element each renders:

| `type` | Use it for | Renders | Terminal event |
|---|---|---|---|
| `confirm` | Approve / decline an action | `kai-confirm` | `action` |
| `choice` | Pick one option from a list | `kai-choice` | `action` |
| `form` | Collect structured input (JSON-Schema form) | `kai-form` | `submit` |
| `tasks` | Select from a proposed plan | `kai-tasks` | `submit` |
| `link` | Preview a URL (title/desc/image) | `kai-link-preview` | — |
| `embed` | Lazy media embed (YouTube, Vimeo) | `kai-embed` | — |
| `artifact` | Frame a generated page plus the source files behind it | `kai-artifact` | — |

`artifact` reaches a deliberately narrow slice of the `<kai-artifact>` element: an envelope sets `src`, `files`, `tab`, `activeFile`, `displayUrl` and `height`, never the iframe `sandbox`, the toolbar composition or the view state. A model says what to show; the host keeps control of how the viewer behaves. `tab` and `activeFile` seed the first render only, so revising the card leaves a user who has since switched views where they are.

Each `data` shape has a published **JSON Schema**, importable as `cardSchemas` from `@kitn.ai/ui/schemas` (or as a raw document, `@kitn.ai/ui/schemas/confirm.schema.json`, for a backend that is not JavaScript). A `confirm` envelope looks like:

```json
{
  "type": "confirm",
  "id": "deploy-1",
  "title": "Deploy to production?",
  "data": {
    "body": "Applies 2 migrations and restarts 3 services.",
    "tone": "danger",
    "actions": [
      { "id": "deploy", "label": "Deploy now", "style": "primary", "default": true },
      { "id": "cancel", "label": "Cancel" }
    ]
  }
}
```

## Prepping your model

There's **no special skill, agent framework, or fine-tune required** — a card is just JSON your model emits and the kit renders. You need two things: the model must (1) know the card types and (2) produce `data` that matches the type's schema. Two practical ways:

### Tool calling (recommended for agents)

Give the model **one tool per card type**, with the tool's parameters set to that card's `data` schema. `cardTools` builds those definitions from the shipped schemas and `cardFromToolCall` maps the call back, so both halves come off one contract:

```ts

const tools = cardTools({ provider: 'anthropic' }); // one per card type, named kai_*

// When the model calls one, the call IS the envelope.
function onToolCall(call) {
  const card = cardFromToolCall(call.name, call.input ?? {}, { id: call.id });
  if (card) cards.cards = [...cards.cards, card];
}
```

The schema guides the model, it does not constrain it. Provider strict modes cannot express any of the seven card types today, so validate what comes back rather than assuming the arguments match. [Schemas as tool definitions](/guides/schemas-as-tools/) has the detail, including why re-using the provider's tool call id is what lets a model revise a card in place.

### Structured output / JSON mode

If you'd rather have the model return envelopes directly, constrain its output to the
`CardEnvelope` schema (or a union of your enabled types) and parse them out of the response. Validate before rendering — see below.

> **tip:** 
Tell the model **when** to reach for a card, not just that it can. e.g. "Use `confirm` before any irreversible action; use `form` to collect more than two fields; otherwise answer in text." Keep `id` stable across a turn so a streamed update lands on the same card.

### Validate before you render

`<kai-cards>` already validates a built-in card's `data` before rendering it and emits `{ kind: 'error' }` when it fails. To check earlier, on the server, use the same validator against the same schema:

```ts

const result = validateAgainstSchema(cardSchemas.confirm, envelope.data); // schema first
if (!result.valid) {
  // result.errors is a string[] — hand it back to the model to fix the card
}
```

It implements a lean keyword subset: `allOf`, `anyOf`, `oneOf`, `format` and `additionalProperties` are not enforced. [Schemas as tool definitions](/guides/schemas-as-tools/#validation-and-what-it-misses) lists what that lets through.

## Wiring the host

Render envelopes with `<kai-cards>` and route every interaction through a `CardPolicy`:

```html
<kai-cards></kai-cards>
<script type="module">

  await customElements.whenDefined('kai-cards');

  const cards = document.querySelector('kai-cards');

  cards.cards = [/* envelopes from your model */];

  cards.policy = {
    onAction: (cardId, action, payload) => continueTurn(cardId, { action, payload }),
    onSubmit: (cardId, data) => continueTurn(cardId, { data }),
  };
</script>
```

`onAction` (confirm/choice) and `onSubmit` (form/tasks) are the **terminal** verbs — that's the user's answer. Feed it back into your next model call (as the tool result for the matching `id`, or as a new user turn) so the agent can continue. In React use `<Chat>`-level handlers; in SolidJS use `renderCard` / `<CardRenderer>` inside a `<CardProvider>`.

## Keep resolved cards across reloads

A card flips to a read-only view the moment the user acts. To make that survive a reload, persist the `resolution` with `applyResolution` and store the array:

```ts

cards.addEventListener('kai-card', (e) => {
  cards.cards = applyResolution(cards.cards, e.detail); // resolved → re-hydrates read-only
  save(cards.cards);
});
```

It's pure and safe on every event — non-terminal events and unknown ids return the array unchanged.

## Your own card types

The built-ins cover most flows, but the `type → tag` map is open. Register an element for a new type and merge it in:

```ts

const types = mergeCardTags({ chart: 'my-chart-card' }); // built-ins + yours
// el.types = types  →  <kai-cards> now renders `chart` envelopes with <my-chart-card>
```

For **provider-owned, cross-origin** cards you don't want to bundle, render them in a sandboxed iframe with `kai-remote` — same envelope, same policy, over `postMessage`. See [remote cards](/examples/remote-cards/).

## Next

- [Generative UI cards](/patterns/generative-ui-cards/) — the rendering pattern, live.
- [confirm](/components/confirm/) · [choice](/components/choice/) · [form](/components/form/) · [tasks](/components/tasks/) — per-card APIs.
