Elements

Generative UI

A styled component library for rendering structured generative UI output.

fig. 01

Installation

Install manually
npx shadcn@latest add "@assistant-ui/generative-ui"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

styledGenerativeUILibrary is @assistant-ui/generative-ui's default component vocabulary (headings, text, cards, lists, and more) with three entries swapped: its Markdown component renders with react-markdown and GitHub-flavored markdown instead of dumping the raw source as plain text, Diagram draws its Mermaid source with the Mermaid diagram element, and Map puts its places on the Geo map element. With a runtime the library backs a tool the model calls to render structured UI; standalone you call renderGenerativeUI yourself with any spec built against the same library.

Getting started

The library only supplies components; something still has to expose them to the model and mount the result when it calls them. JSONGenerativeUI builds that bridge: a tool whose parameters come from the library's Zod schemas, and whose render draws the model's response through the library.

Build a toolkit around the library

app/toolkit.tsx
import { defineToolkit } from "@assistant-ui/react";
import { JSONGenerativeUI } from "@assistant-ui/generative-ui/react";
import { styledGenerativeUILibrary } from "@/components/assistant-ui/elements/generative-ui";

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

export default defineToolkit({
  present: generative.present(),
});

Register it on your runtime

app/page.tsx
"use client";

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

const config = AuiConfig({ tools: Tools({ toolkit }) });

export default function Page() {
  const runtime = useChatRuntime({
    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
  });
  return (
    <AssistantRuntimeProvider config={config} runtime={runtime}>
      <Thread />
    </AssistantRuntimeProvider>
  );
}

The model can now call present with a { _type, ...props } tree; it renders through styledGenerativeUILibrary, so a Markdown node comes out fully formatted instead of as raw text.

Examples

See the override in action

defaultGenerativeUILibrary.Markdown renders value as plain text; styledGenerativeUILibrary.Markdown parses the same string as GitHub-flavored markdown. Both wrap the result in the same <div data-aui="markdown">, so any styling already targeting that attribute keeps applying.

import { defaultGenerativeUILibrary, renderGenerativeUI } from "@assistant-ui/generative-ui/react";
import { styledGenerativeUILibrary } from "@/components/assistant-ui/elements/generative-ui";

renderGenerativeUI({ $type: "Markdown", value: "**bold**" }, defaultGenerativeUILibrary); // <div data-aui="markdown">**bold**</div>
renderGenerativeUI({ $type: "Markdown", value: "**bold**" }, styledGenerativeUILibrary); // <div data-aui="markdown"><strong>bold</strong></div>

Extend the library with your own component

Add to it with a plain object spread. No special wrapper is required for a library that is not split across a client and a server build.

app/toolkit.tsx
import { JSONGenerativeUI } from "@assistant-ui/generative-ui/react";
import { styledGenerativeUILibrary } from "@/components/assistant-ui/elements/generative-ui";
import { z } from "zod";

const library = {
  ...styledGenerativeUILibrary,
  Callout: {
    description: "A highlighted note.",
    properties: z.object({ text: z.string() }),
    render: ({ text }: { text: string }) => <div data-aui="callout">{text}</div>,
  },
};

const generative = new JSONGenerativeUI({ library });

API reference

JSONGenerativeUI

MemberParametersDescription
new JSONGenerativeUI(options){ library: GenerativeUILibrary; actions?: ActionRegistry }Builds the tool's parameter schema once, from the library's Zod properties.
.present(options?){ display?: "standalone" }, returns a PresentToolA frontend tool: parameters are the library's schema, and calling it renders the model's { _type, ...props } tree through the library. display: "standalone" surfaces the result outside the chain-of-thought trace instead of inline.
.promptUser()none, returns a PromptUserToolThe same rendering, as a human-in-the-loop tool: the model pauses until the rendered UI supplies a result.

Register the resulting tool the way you register any other: defineToolkit({ present: generative.present() }), then Tools({ toolkit }) into your AuiConfig.