Quickstart
The fastest path from ToonUI install to a working AI-native UI flow.
This is the shortest happy path:
- create the protocol on the server
- inject the ToonUI prompt into the model
- render the assistant output on the client
- turn clicks and submits into structured app input
What you are building
After this page, you should have:
- a model that can emit
toon-uiblocks - a client that can render them
- typed interactions that return to your chat or app loop
- a cleaner UX than plain text alone
Server: teach the model the language
import { createToonProtocol } from '@toon-ui/core';
const toon = createToonProtocol();
const system = [
toon.prompt,
'You are helping users compare products.',
'Use ToonUI when structured UI reduces friction.',
'Available tools:',
'- searchProducts(query)',
].join('\n\n');This does ONE important thing:
it teaches the model how to describe UI without giving it control of your application.
Do NOT replace toon.prompt with ad-hoc prose rules unless you already understand the prompt architecture and tradeoffs.
If you want to inspect the full prompt later, see Full Generated Prompt.
Client: render assistant output
'use client';
import { ToonMessage, createToonClient } from '@toon-ui/toon-ui';
const toon = createToonClient();
export function AssistantMessage({
content,
append,
}: {
content: string;
append: (message: unknown) => void;
}) {
return (
<ToonMessage
content={content}
runtime={toon}
onReply={(payload) => append(toon.messages.toUIMessage(payload))}
onSubmit={(payload) => append(toon.messages.toUIMessage(payload))}
/>
);
}This renders mixed assistant output:
- normal markdown
- fenced
toon-uiblocks - reply and submit interactions
Example model output
I found two strong options for your team.
```toon-ui
card "Recommended plans":
text "Choose the plan that best fits your current growth stage."
button secondary "Compare plans" reply="compare:plans"
button primary "Talk to sales" reply="sales:contact"
```What happens next
- the model emits a ToonUI block
ToonMessagerenders it- the user clicks a button or submits a form
- ToonUI creates a typed payload
- your app decides what to do with that payload
That last step is the boundary that matters.
Reinject interactions into your app loop
Most apps use one of these two reinjection paths:
| API | Use it when |
|---|---|
toon.messages.toUIMessage(payload) | Your UI stores chat/message objects |
toon.messages.toModelMessage(payload) | The next step goes back to the LLM loop |
Example:
onReply={(payload) => {
const next = toon.messages.toModelMessage(payload);
sendToModel(next);
}}Validate before trusting output
If your app needs stricter guarantees, validate the generated block before rendering or persisting it.
const blocks = toon.extractBlocks(content);
for (const block of blocks) {
const ast = toon.parse(block.raw);
const result = toon.validate(ast);
if (!result.ok) {
console.error(result.errors);
}
}Use this for:
- tests
- fixtures
- server-side checks
- debugging generation quality
When to switch to @toon-ui/react
Stay on @toon-ui/toon-ui if you want the fastest path.
Move to @toon-ui/react when you need:
- explicit adapter ownership
- custom components
- stricter design-system control
Success checklist
-
toon.promptis in the system prompt -
createToonClient()is running on the client - assistant content renders through
ToonMessage - reply actions produce payloads
- submit actions produce payloads
- your app handles the next step after interaction
What NOT to do
Do NOT put business logic inside ToonUI blocks.
Wrong mindset:
- "the model executes the workflow"
Correct mindset:
- "the model describes the interaction surface"
- "the host app owns behavior"