ToonUI
Start Here

Configuration

Detailed setup guide for the server prompt, client runtime, adapter configuration, and the main public APIs ToonUI exposes.

This page is the missing bridge between “I installed it” and “I understand how to configure it correctly”.

The core configuration idea

A correct ToonUI setup has four parts:

  1. server protocol
  2. model prompt composition
  3. client runtime
  4. interaction reinjection

If one of those is vague, the integration becomes fragile.

1. Configure the server protocol

The canonical server entrypoint is createToonProtocol().

import { createToonProtocol } from '@toon-ui/core';

const toon = createToonProtocol();

This exposes:

  • toon.prompt
  • toon.rules
  • toon.catalog
  • toon.events
  • toon.messages

What each one is for

APIPurpose
toon.promptcanonical instruction set for the model
toon.rulesstructured language rules for custom prompt composition
toon.catalogofficial catalog of components, variants, and examples
toon.eventsfactories for reply and submit payloads
toon.messagesconverters from payloads to model/UI message shapes

2. Configure the model prompt correctly

The most common good setup is:

import { createToonProtocol } from '@toon-ui/core';

const toon = createToonProtocol();

const businessRules = [
  'You are helping users manage products and sales.',
  'Use ToonUI when structured UI reduces back-and-forth.',
  'Do not invent database operations.',
].join('\n');

const toolDescription = [
  'Available tools:',
  '- searchProducts(query)',
  '- createProduct(input)',
  '- updateInventory(productId, quantity)',
].join('\n');

export const systemPrompt = [
  toon.prompt,
  '',
  toolDescription,
  '',
  businessRules,
].join('\n');

Why this structure works

  • ToonUI defines the UI language
  • your app defines business behavior
  • tool descriptions define host capabilities

That separation is CLEAN architecture, not convenience theater.

3. Configure the client runtime

You have two main client entrypoints.

Option A — createToonClient()

Use this for the simplest public setup:

import { createToonClient } from '@toon-ui/toon-ui';

const toon = createToonClient();

This gives you:

  • default adapter
  • protocol APIs
  • parsing/validation helpers
  • rendering compatibility with ToonMessage and ToonRenderer

Option B — createToonReactRuntime()

Use this when your UI layer must be explicit:

import { createToonReactAdapter, createToonReactRuntime } from '@toon-ui/react';

const adapter = createToonReactAdapter({
  level: 'default',
});

const toon = createToonReactRuntime({
  adapter,
});

4. Configure adapter behavior

The adapter is where React ownership becomes explicit.

minimal

const adapter = createToonReactAdapter({
  level: 'minimal',
  components: {
    button: MyButton,
  },
});

Use it when you want to build your registry manually.

default

const adapter = createToonReactAdapter({
  level: 'default',
  components: {
    button: MyButton,
  },
});

Use it when you want built-in defaults plus selective overrides.

strict

const adapter = createToonReactAdapter({
  level: 'strict',
  components: {
    text: MyText,
    card: MyCard,
    form: MyForm,
    field: MyField,
    button: MyButton,
    confirm: MyConfirm,
    list: MyList,
    item: MyItem,
    badge: MyBadge,
    alert: MyAlert,
    table: MyTable,
  },
});

Use it when missing component coverage should fail immediately.

5. Configure layout spacing

The React runtime accepts layout options:

const toon = createToonReactRuntime({
  adapter,
  layout: {
    messageGap: 20,
    blockGap: 16,
    nodeGap: 10,
  },
});

Use this when you want to tune:

  • spacing between assistant message sections
  • spacing between blocks
  • spacing between nodes inside blocks

6. Configure rendering

High-level rendering with ToonMessage

<ToonMessage
  content={assistantContent}
  runtime={toon}
  onReply={(payload) => append(toon.messages.toUIMessage(payload))}
  onSubmit={(payload) => append(toon.messages.toUIMessage(payload))}
/>

Use this when the assistant returns:

  • prose
  • markdown
  • fenced toon-ui blocks

Lower-level rendering with ToonRenderer

Use ToonRenderer when you want finer control over where the structured block appears relative to the rest of the UI.

7. Configure interaction reinjection

This is one of the most important parts of the whole integration.

UI message reinjection

const next = toon.messages.toUIMessage(payload);
append(next);

Use this when the interaction should be appended to a UI/chat state list.

Model message reinjection

const next = toon.messages.toModelMessage(payload);
sendToModel(next);

Use this when the interaction becomes the next user message for the model loop.

Raw content conversion

const content = toon.messages.toContent(payload);

Use this when you only need the normalized interaction string.

8. Configure custom components safely

If you replace components, use the built-in helpers when possible.

Example for buttons:

import {
  createToonAdapter,
  createToonClient,
  getToonButtonProps,
  type ToonButtonComponentProps,
} from '@toon-ui/toon-ui';

function MyButton(props: ToonButtonComponentProps) {
  return (
    <button
      className="rounded-md border px-3 py-2"
      {...getToonButtonProps(props)}
    >
      {props.node.label}
    </button>
  );
}

const adapter = createToonAdapter({
  level: 'default',
  components: {
    button: MyButton,
  },
});

const toon = createToonClient({ adapter });

Why this matters:

  • you keep ToonUI interaction wiring intact
  • you customize visuals without breaking semantics

9. Configure parsing and validation in stricter environments

If you want a more defensive setup:

const blocks = toon.extractBlocks(content);

for (const block of blocks) {
  const ast = toon.parse(block.raw);
  const result = toon.validate(ast);

  if (!result.ok) {
    throw new Error(
      `Invalid ToonUI: ${result.errors.map((error) => error.message).join(', ')}`
    );
  }
}

Use this in:

  • tests
  • fixtures
  • strict server pipelines
  • debugging sessions

10. What @toon-ui/toon-ui exposes

The main public client package exposes:

  • createToonClient()
  • createToonAdapter()
  • ToonMessage
  • ToonRenderer
  • extractToonMarkdown
  • all public exports from @toon-ui/core
  • all public exports from @toon-ui/react

That means it is both:

  • convenience layer
  • public umbrella entrypoint

11. What @toon-ui/react exposes

The React package exposes the explicit runtime layer:

  • createToonReactAdapter()
  • createToonAdapter()
  • mergeToonComponentRegistry()
  • createToonReactRuntime()
  • assertToonReactAdapter()
  • ToonProvider
  • ToonMessage
  • ToonRenderer
  • extractToonMarkdown
  • getToonFieldId()
  • getToonButtonProps()
  • getToonInputProps()
  • getToonTextareaProps()
  • getToonCheckboxProps()
  • useToonUI()
  • useToonReply()
  • useToonSubmit()
  • useToonAction()
  • basicPreset()

12. What @toon-ui/core exposes

The core package exposes the protocol and contract layer:

  • catalog exports
  • types exports
  • parseToonUI()
  • ToonSyntaxError
  • validateToonUI()
  • extractToonBlocks()
  • createPrompt()
  • createComponentPrompt()
  • createSyntaxPrompt()
  • createFallbackPrompt()
  • createSafetyPrompt()
  • createExamplesPrompt()
  • createRules()
  • createToonProtocol()
  • createToonCoreRuntime()

If you want the default recommendation:

  • server: createToonProtocol()
  • client: createToonClient()
  • rendering: ToonMessage
  • reinjection: toon.messages.toUIMessage() for UI state
  • stricter loops: toon.messages.toModelMessage() for model continuation

14. Final configuration checklist

  • toon.prompt is included in the system prompt
  • business rules stay outside ToonUI itself
  • the client runtime is created exactly once in the right place
  • interaction payloads are reinjected intentionally
  • adapter level is chosen deliberately, not by guess
  • parsing/validation strategy is defined for your environment
  1. Mental Model
  2. Boundaries
  3. Protocol and Types
  4. React Runtime and Hooks

On this page