# Generative UI
URL: /docs/tools/generative-ui

Let the model compose an interface at runtime from a component vocabulary you ship, using the present tool from @assistant-ui/react-generative-ui.

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

With generative UI, you ship a **vocabulary** of components and let the model assemble them, instead of writing one component per tool. The model calls a single `present` tool whose arguments are a JSON tree of component names and props, and assistant-ui renders that tree.

The package ships a default vocabulary of 29 components (cards, facts, tables, charts, forms, controls), so the shortest useful setup registers one tool and writes no components at all.

## Which generative UI pattern?

assistant-ui uses "generative UI" for more than one thing. Two questions separate them: does the **model** compose the layout or do you bind it ahead of time, and does the UI originate from a **tool call** or from a part your backend emits. Pick the row that matches what you are building:

| Pattern                                                            | API                                         | Best for                                                                     |
| ------------------------------------------------------------------ | ------------------------------------------- | ---------------------------------------------------------------------------- |
| **The `present` tool** (this page)                                 | `JSONGenerativeUI` + `present`              | The model composes dashboards, cards, and layouts from a vocabulary you ship |
| **[Tool UI](/docs/tools/tool-ui)**                                 | toolkit `render`                            | A widget tied to a tool you already know about (forms, pickers, charts)      |
| **[Generative UI primitive](/docs/tools/generative-ui-primitive)** | `MessagePrimitive.GenerativeUI` + allowlist | A backend that already emits `generative-ui` message parts                   |
| **[LangGraph data UI](/docs/runtimes/langgraph/generative-ui)**    | `makeAssistantDataUI` + `ui_message`        | LangGraph agents emitting UI on the LangGraph stream                         |

The first two are tool-driven, so the model decides when UI appears; the last two are backend-driven, so your agent does. The backend-driven pair splits on transport rather than on either axis: the primitive reads a `generative-ui` message part, while LangGraph data UI reads `push_ui_message` off the LangGraph stream. `present` and the primitive both take a JSON component tree, but in [non-interchangeable shapes](/docs/tools/generative-ui-primitive#spec-shape). A third-party option also exists in the tool-driven space: [OpenUI](/docs/tools/openui) publishes an integration that streams OpenUI Lang through the same shape.

Browse every component the default vocabulary offers in the [component vocabulary reference](/elements/vocabulary), and see finished compositions in the [Generative section of Elements](/elements).

## Quick start

1. ### Install the package

   ```bash
   npm install @assistant-ui/react-generative-ui
   ```

   The vocabulary renders as unstyled semantic HTML with `data-aui` attributes. Add the styled library to get the shipped look:

   With the style-aware registry configured in components.json ("@assistant-ui": "https\://r.assistant-ui.com/styles/{style}/{name}.json"), the flavor resolves from the project style automatically:

   ```bash
   npx shadcn@latest add @assistant-ui/generative-ui
   ```

   Or add by direct URL without registry configuration:

   ```bash
   npx shadcn@latest add https://r.assistant-ui.com/base/generative-ui.json
   ```

   Or install manually:

   ```bash
   npm install @assistant-ui/react-generative-ui@^0.0.24 react-markdown remark-gfm
   ```

   Then copy these files (packaged registry content; the source links show where each ships from):

   - [components/assistant-ui/elements/generative-ui.tsx](https://r.assistant-ui.com/base/files/generative-ui/components/assistant-ui/elements/generative-ui.tsx) ([source](https://github.com/assistant-ui/assistant-ui/blob/main/packages/ui/src/components/react/assistant-ui/elements/generative-ui.tsx))

   ```bash
   curl -fsSL --create-dirs \
     -o 'components/assistant-ui/elements/generative-ui.tsx' https://r.assistant-ui.com/base/files/generative-ui/components/assistant-ui/elements/generative-ui.tsx
   ```

   This merges the vocabulary stylesheet and theme variables into your CSS, which is what the rest of this page assumes, and lands `components/assistant-ui/elements/generative-ui.tsx`. That file exports `styledGenerativeUILibrary`, whose only difference from the default vocabulary is a real markdown renderer; [Styling](#styling) wires it in.

2. ### Enable the compiler

   The `"use generative"` directive lets one file declare tools that both the browser and your server route can import: the compiler strips the browser-only halves out of the server build and the schemas out of the client build.

   ```
   import { withAui } from "@assistant-ui/next";

   export default withAui({
     /* your Next config */
   });
   ```

   Vite and TanStack Start use `aui()` from `@assistant-ui/vite`; Expo and bare React Native use `withAui` from `@assistant-ui/metro`.

3. ### Expose the vocabulary as a tool

   `JSONGenerativeUI` turns a component library into the model-facing schema for `present`. Register the result on a toolkit like any other tool.

   ```
   "use generative";

   import { defineToolkit } from "@assistant-ui/react";
   import {
     JSONGenerativeUI,
     defaultGenerativeUILibrary,
   } from "@assistant-ui/react-generative-ui";

   const generative = new JSONGenerativeUI({
     library: defaultGenerativeUILibrary,
   });

   export default defineToolkit({
     present: generative.present({ display: "standalone" }),
   });
   ```

   `display: "standalone"` renders the result on its own surface, outside the chain-of-thought trace. Omit it to render inline.

4. ### Register the toolkit on the client

   ```
   "use client";

   import {
     AssistantRuntimeProvider,
     AuiConfig,
     Tools,
   } from "@assistant-ui/react";
   import { useChatRuntime } from "@assistant-ui/ai-sdk";
   import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
   import toolkit from "./toolkit";

   export function MyRuntimeProvider({
     children,
   }: {
     children: React.ReactNode;
   }) {
     const runtime = useChatRuntime({
       sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
     });
     const config = AuiConfig({ tools: Tools({ toolkit }) });

     return (
       <AssistantRuntimeProvider runtime={runtime} config={config}>
         {children}
       </AssistantRuntimeProvider>
     );
   }
   ```

   > [!warn]
   >
   > `present` is a frontend tool: it resolves in the browser, and the run only continues once its result is sent back. Without `sendAutomaticallyWhen`, the UI renders and the conversation then stops.

5. ### Serve the schema

   The route imports the same toolkit module. The compiler resolves that import to the server build, so only the schemas cross over and no browser code enters your server bundle.

   ```
   import { openai } from "@ai-sdk/openai";
   import { AISDKToolkit } from "@assistant-ui/ai-sdk";
   import { convertToModelMessages, stepCountIs, streamText } from "ai";
   import toolkit from "@/app/toolkit";

   const aiToolkit = new AISDKToolkit({ toolkit });

   export async function POST(req: Request) {
     const { messages, tools } = await req.json();

     const result = streamText({
       model: openai("gpt-6-luna"),
       messages: await convertToModelMessages(messages),
       stopWhen: stepCountIs(10),
       tools: await aiToolkit.tools({ frontend: tools }),
     });

     return result.toUIMessageStreamResponse();
   }
   ```

   `stopWhen` matters here for the same reason `sendAutomaticallyWhen` does: rendering the UI is one step, and the model needs another to say anything after it.

Ask for something the vocabulary can express, for example "show me a sales dashboard for the last six months", and the model will answer with a rendered composition. The complete setup runs in [`examples/with-generative-ui`](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-generative-ui).

## What the model emits

Every node is a flat object: `$type` names the component, `children` nests, and every other key is a prop.

```
{
  "$type": "Card",
  "title": "Q3 revenue",
  "children": [
    {
      "$type": "Row",
      "children": [
        { "$type": "Fact", "label": "Bookings", "value": "$1.2M" },
        { "$type": "Fact", "label": "Growth", "value": "+18%" }
      ]
    },
    {
      "$type": "Chart",
      "variant": "bar",
      "showAxis": true,
      "data": [
        { "label": "Jul", "value": 22 },
        { "label": "Aug", "value": 26 },
        { "label": "Sep", "value": 31 }
      ]
    }
  ]
}
```

Keys beginning with `$` are reserved by the framework (`$type`, `$key`, `$action`, and the injected `$status`), and `children` follows the JSX convention. Every other key is yours, so a component is free to declare props named `type`, `status`, or `variant` without colliding.

## Extending the vocabulary

`defineGenerativeComponents` adds your own components. Each one declares a zod schema for its props, a description the model reads, and a render function.

```
"use generative";

import { z } from "zod";
import {
  JSONGenerativeUI,
  defaultGenerativeUILibrary,
  defineGenerativeComponents,
} from "@assistant-ui/react-generative-ui";
import { WeatherCard } from "@/components/weather-card";

const generative = new JSONGenerativeUI({
  library: {
    ...defaultGenerativeUILibrary,
    ...defineGenerativeComponents({
      Weather: {
        description: "Show a weather card for a `get_weather` result.",
        properties: z.object({
          id: z.string().describe("The `id` returned by `get_weather`."),
        }),
        render: (props) => <WeatherCard {...props} />,
      },
    }),
  },
});
```

Set `streamProperties: true` alongside `properties` to receive partially-filled props while the model is still writing them. `render` then sees `Partial<P>` and an injected `$status` of `"streaming"`, and the full props once it turns `"done"`. Components opt out by default and render only once their props are complete.

> [!warn]
>
> Inside a `"use generative"` module, a `"use client"` module may be referenced **only** as the `render` value of an inline `defineGenerativeComponents` literal. The compiler drops `render` and the imports only it uses from the server build; referencing such a module from `properties`, `description`, a spread, or a top-level constant leaks a client reference into the server graph and breaks schema generation. `defaultGenerativeUILibrary` is safe on both builds.

## Actions

Pass an action registry to let rendered nodes call back into your app. The model puts an `$action` object on a node, and its `type` is matched against your handlers.

```
import {
  JSONGenerativeUI,
  createActionRegistry,
  defaultGenerativeUILibrary,
} from "@assistant-ui/react-generative-ui";

const actions = createActionRegistry({
  purchase: async ({ payload }) => {
    await checkout(payload);
  },
});

const generative = new JSONGenerativeUI({
  library: defaultGenerativeUILibrary,
  actions,
});
```

```
{
  "$type": "Button",
  "label": "Buy",
  "$action": { "type": "purchase", "sku": "pro-plan" }
}
```

### Undo window

Set `undoable` for sends and other hard-to-reverse actions. The button waits five seconds, shows Undo with a countdown, and another click or Escape cancels it.

```
{
  "$type": "Button",
  "label": "Delete order",
  "undoable": true,
  "$action": { "type": "delete_order", "orderId": "48213" }
}
```

`Select`, `Input`, `DatePicker`, `Checkbox`, `Slider`, `RadioGroup`, and `CheckboxGroup` add the user's value as `$input` when they fire the action; `Slider` sends a number and a `CheckboxGroup` sends the array of its checked option values. `Form`, and a `Card` with `asForm` set, add an object keyed by each control's `name` instead. On a `Card` there is no card-level `$action`: the collected object is dispatched through `confirm.$action`, while `cancel.$action` always fires without `$input`. Unknown action types are ignored and warn in development.

`Slider` takes `min`, `max`, an optional `step`, `defaultValue`, and `unit`; it shows the live value and commits only when the adjustment finishes. Set `Checkbox` `variant` to `"switch"` for a switch presentation without changing its boolean value. `RadioGroup` and `CheckboxGroup` options can add a short `description`, rendered under the label.

Any value inside `$action` can be a `{ "$field": name }` reference instead, which resolves to a control's current value when the action fires. It reads the control with that `name` in the nearest enclosing `Form` or `Card` with `asForm`, or else anywhere in the same `JSONGenerativeUI` render, collected the way a `Form` collects it. When no control holds a value, a reference written as `{ "$field": name, "fallback": value }` resolves to its `fallback`, and one without a fallback is dropped. A component outside the default vocabulary that dispatches `$action` itself reads no control, so each reference in it resolves the same way. A tree rendered with `renderGenerativeUI` directly resolves references only inside one of those forms or an element marked `data-aui="root"`, so they never read a host page's own form. `Input` and `Select` take a `defaultValue`, so a button can send an edited value without a form:

```
{
  "$type": "Col",
  "children": [
    {
      "$type": "Input",
      "name": "note",
      "label": "Delivery note",
      "defaultValue": "Leave at the door"
    },
    {
      "$type": "Button",
      "label": "Save",
      "$action": { "type": "save_note", "note": { "$field": "note" } }
    }
  ]
}
```

On Teams, `decodeSubmitData` resolves these references from the card's inputs. On Slack, pass the interaction payload's `state.values` alongside its action to `decodeBlockAction` to resolve references from named controls; a missing field uses its fallback when provided.

`Input` accepts an optional `inputType` of `"text"`, `"password"`, or `"number"` for a single line control, defaulting to text. A number input uses `step="any"` and keeps its value as a string, including decimal values such as `"12.5"` and `""` when cleared. Setting `multiline` renders a textarea instead.

`DatePicker` accepts `inputType` `"date"` (the default), `"time"`, or `"datetime"`. Its own `$action` runs once per committed value: a pick from the native picker commits at once, and a typed value commits on blur or on Enter outside a form. Date values use `YYYY-MM-DD`; time values use `HH:mm` or `HH:mm:ss`; datetime values use `YYYY-MM-DDTHH:mm` with optional seconds, fractional seconds, and an offset (`Z` or `±HH:mm`). A datetime with an offset displays in the viewer's time zone and submits with the same offset; one without an offset submits unchanged. The [A2UI converter](/docs/tools/a2ui#component-mapping) maps `DateTimeInput` to the matching type, and uses a text `Input` when its value cannot fit that type. A single choice A2UI `ChoicePicker` maps to `RadioGroup` even for a `chips` display hint, preserving an empty selection as `[]` and a selected value as a one item string array; `multipleSelection` maps to `CheckboxGroup`.

A control resets to its initial value (`defaultValue`, `defaultChecked`, or a `DatePicker`'s `value`) whenever a re-render changes that value, and otherwise keeps what the user entered.

Each `renderGenerativeUI` or `JSONGenerativeUI` render scopes native radio names, so separate cards can reuse a logical `name` without clearing each other's selection. Inside the same render and form scope, radio groups with the same logical name share one selection. The logical name remains the key for form values and `$field` references. If you mount multiple independent React apps on the same page, configure distinct [`identifierPrefix` values](https://react.dev/reference/react/useId#using-the-same-id-prefix-on-the-client-and-the-server) and use each app's same prefix for server rendering and hydration.

Each dispatched action, including its `$input`, is recorded on the tool call on a best-effort basis. Recording depends on the runtime and the interaction log's limits, and it never delays or prevents dispatch. When the UI is registered through `promptUser()`, a handler's non-`undefined` return value becomes the `prompt_user` tool result only when that call has no result yet. Whether the run then continues depends on the runtime, including how it treats human tools. `present` actions never create a tool result.

Once answered, a `prompt_user` locks its controls and shows the submitted values. Only values submitted through a `Form` or a `Card` with `asForm` are restored after a reload.

Without a registry, the tree still renders and model-emitted actions degrade to a no-op.

## Charts

`Chart` accepts positive and negative values. Its scale always includes zero, so bars grow upward or downward from the zero line, areas fill to that line, and stacked bars and areas keep positive and negative totals on their respective sides. A chart that contains both positive and negative values draws a zero baseline. When `showAxis` is set, the ticks include zero and use round values for a mixed domain.

Bars and data points in line and area charts expose a native tooltip with the point label and formatted value. A point without a label shows its position, counting from 1. Tooltips on named series also include the series label. Sparklines remain compact and do not add point targets.

Use `series[].color` to set an individual series to `emphasis`, `secondary`, `alpha-70`, `white`, `white-70`, or `white-50`. The same color fills that series' legend swatch, while the legend label stays muted.

## Tables and facts

`Table` takes `columns`, `rows`, and optional `sortable`. Each column has a required `label`, an optional `align` of `"start"` or `"end"`, and an optional `format`. A format has a `kind` of `"number"`, `"currency"`, `"percent"`, or `"date"`; numeric kinds can set `decimals` from 0 to 20, currency also needs a `currency` code, and percent takes a fraction, so `0.25` renders as 25%. Number, currency, and percent columns end-align by default. Dates accept ISO strings or epoch numbers. Values with the wrong type, or a format the platform cannot apply, stay as their raw text. A sortable header cycles ascending, descending, then the original row order, and marks the sorted column with an arrow.

`Fact` takes `label` and `value`, plus optional `delta`, `trend`, and `upIsGood`. `delta` renders as a change chip beside the value. Set `trend` to `"up"`, `"down"`, or `"flat"` to choose its direction, or omit it to infer down from a leading minus sign. The arrow carries the direction, so a sign that agrees with it is dropped from the chip. `upIsGood` defaults to `true` and flips which direction is positive when set to `false`.

## Carousels

Carousel paging controls appear only when cards overflow the available width. Each press moves one card, and motion follows the user's reduced motion preference.

## Styling

The vocabulary renders semantic HTML tagged with `data-aui` and `data-aui-*` attributes and ships no styles of its own, so it cannot conflict with your CSS. The `generative-ui` registry item above installs the shipped stylesheet, which is written entirely against your existing theme variables: change `--radius` or `--primary` in your own CSS and the rendered widgets follow.

A `Card` renders as a plain section by default, so several in a row read as one answer rather than a stack of boxes. It takes on a framed surface only where one is warranted: when the model sets a `background`, when `confirm` or `cancel` add a footer whose buttons need a delimited target, or when it is a carousel slot.

To restyle a single component, target its attribute:

```
[data-aui="fact-value"] {
  font-variant-numeric: tabular-nums;
  font-size: 1.125rem;
}
```

Overriding a component's markup rather than its appearance is a library-level change: spread `defaultGenerativeUILibrary` and replace that one entry through `defineGenerativeComponents`. The default `Markdown` renders its source as plain text, which is why the registry item ships a replacement. Wiring it is the same override:

```
import { styledGenerativeUILibrary } from "@/components/assistant-ui/elements/generative-ui";

const markdown = defaultGenerativeUILibrary.Markdown!;

const generative = new JSONGenerativeUI({
  library: {
    ...defaultGenerativeUILibrary,
    ...defineGenerativeComponents({
      Markdown: {
        properties: markdown.properties,
        streamProperties: markdown.streamProperties,
        description: "A markdown string, rendered with GitHub-flavored markdown.",
        render: styledGenerativeUILibrary.Markdown!.render,
      },
    }),
  },
});
```

`styledGenerativeUILibrary` is a `"use client"` module, so this inline `render` is the only place a `"use generative"` file may name it. Passing it directly as `library` works only in a file without the directive.

## Security

The library is the boundary on **which** components can render: the model can only name components you put in it, resolved by lookup with no `eval` and no dynamic import. Unknown names are dropped.

Props are a separate question. Each component's zod schema validates what the model sends, so a component that declares only primitive, display-oriented props cannot receive anything else. When you add a component that takes a URL, a raw HTML string, or anything that becomes executable, validate it inside that component; the schema constrains shape, not intent.

## Beyond the browser

The tree is plain JSON, so it is not tied to the browser. Four React-free subpaths let a server action, queue worker, or webhook handler consume it without pulling React:

- `@assistant-ui/react-generative-ui/ir` holds the tree types, normalization, and token enums.
- `@assistant-ui/react-generative-ui/slack` converts the tree into Block Kit and decodes `block_actions` back into `$action`. See [Generative UI on Slack](/docs/tools/generative-ui-slack).
- `@assistant-ui/react-generative-ui/teams` converts it into an Adaptive Card and decodes the submit payload. See [Generative UI on Microsoft Teams](/docs/tools/generative-ui-teams).
- `@assistant-ui/react-generative-ui/a2ui` consumes [A2UI surfaces over AG-UI](/docs/tools/a2ui).