ToonUI
Getting Started

Quickstart

Build the recommended ToonUI flow with React and AI SDK.

This page builds the happy path:

  1. configure the active ToonUI catalog
  2. generate the model prompt on the server
  3. stream model output with AI SDK
  4. render markdown plus ToonUI in React
  5. send reply/submit interactions back to the chat loop

1. Share the component keys

Create a small shared file for the standard ToonUI components your app supports.

lib/toon-components.ts
export const toonComponentKeys = ['text', 'card', 'form', 'field', 'button', 'table'] as const;

Do not invent component names here. These must be standard components supported by the ToonUI parser.

2. Server: create the protocol

app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { convertToModelMessages, streamText, type UIMessage } from 'ai';
import { createToonProtocol } from '@toon-ui/core';
import { toonComponentKeys } from '@/lib/toon-components';

const toon = createToonProtocol({
  components: toonComponentKeys,
});

const system = [
  toon.prompt,
  'You are helping users compare products.',
  'Use ToonUI when structured UI reduces friction.',
  'Available tools:',
  '- searchProducts(query)',
].join('\n\n');

export async function POST(request: Request) {
  const { messages } = (await request.json()) as { messages: UIMessage[] };

  const result = streamText({
    model: openai('gpt-4o-mini'),
    system,
    messages: await convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}

3. Client: create the React runtime

components/assistant-message.tsx
'use client';

import { ToonMessage, createToonReactRuntime } from '@toon-ui/react';
import { MessageResponse } from '@/components/ai-elements/message';

const toon = createToonReactRuntime({
  components: {
    text: TextComponent,
    card: CardComponent,
    form: FormComponent,
    field: FieldComponent,
    button: ButtonComponent,
    table: TableComponent,
  },
});

export function AssistantMessage({
  content,
  append,
}: {
  content: string;
  append: (message: unknown) => void;
}) {
  return (
    <ToonMessage
      content={content}
      runtime={toon}
      renderMarkdown={(markdown) => <MessageResponse>{markdown}</MessageResponse>}
      renderError={(error) => <MyError details={error.details} />}
      onReply={(payload) => append(toon.messages.toUIMessage(payload))}
      onSubmit={(payload) => append(toon.messages.toUIMessage(payload))}
    />
  );
}

4. What the model can emit

Because the server catalog only enabled text, card, form, field, button, and table, the prompt teaches only that subset.

Example valid output:

Here are the best matches.

```txt
card "Recommended product":
  text "Coca-Cola 400ml has strong availability."
  button primary "View details" reply="view product details"
```

If the model emits a component outside the active catalog, validation rejects it instead of letting unsupported UI render silently.

5. Next step

Read the full Vercel AI SDK guide.

On this page