Tool UI is in active development, and APIs are still coalescing as we refine discover better patterns for conversation-native UIs.
Tool UI is a React component framework for conversation‑native UIs inside AI chats.
Tools return JSON; Tool UI renders it as inline, narrated, referenceable surfaces.
At a glance
Conversation‑native components that live inside messages, optimized for chat width and scroll.
Schema‑first rendering: every surface is driven by a serializable schema with stable IDs.
Assistant‑anchored: the assistant introduces, interprets, and closes each surface.
Stack‑agnostic: works with any LLM/provider and orchestration layer.
Where it fits
Radix/shadcn (primitives) → Tool UI (conversation‑native components & schema) → AI SDK / LangGraph / etc. (LLM orchestration)
Who it’s for
React devs & design engineers building LLM chat apps.
Teams who want predictable, accessible UI that the assistant can reference (“the second row”).
Anyone who wants typed, schema‑validated tool outputs instead of brittle HTML strings.
Conversation coherence: Every important action ↔ one canonical sentence; side effects yield a durable receipt (status, summary, identifiers, timestamp)
How it works
The assistant calls a tool, the tool returns JSON matching a schema, and the UI renders inline.
Loading diagram...
Minimal example
Server side: Define a tool that returns schema-validated JSON.
What this demonstrates:
serializableMediaCardSchema from @tool-ui/media-card ensures type-safe output
Actions are defined with both UI labels and canonical sentences for conversation coherence
The schema guarantees the frontend receives reconstructable, addressable data
import { streamText, tool } from "ai";import { openai } from "@ai-sdk/openai";import { z } from "zod";import { serializableMediaCardSchema } from "@/components/tool-ui/media-card/schema";export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: openai("gpt-4o"), messages, tools: { previewLink: tool({ description: "Show a preview card for a URL", inputSchema: z.object({ url: z.string().url() }), outputSchema: serializableMediaCardSchema, async execute({ url }) { return { id: "link-preview-1", assetId: "link-1", kind: "link", href: url, title: "Example Site", description: "A description of the linked content", thumb: "https://example.com/image.jpg", }; }, }), }, }); return result.toUIMessageStreamResponse();}
Client side: Register the component and let assistant-ui handle rendering.
What this demonstrates:
makeAssistantToolUI connects the tool name to a React component
The result object is automatically typed and validated against your schema