Find, install, configure, and integrate Tool UI components in React apps using shadcn registry entries, compatibility checks, scaffolded runtime wiring, toolkit setup with assistant-ui, and troubleshooting workflows. Use when developers ask to add one or more Tool UI components, choose components for a use case, verify compatibility, wire a toolkit in a codebase, or integrate Tool UI payloads into assistant-ui or an existing chat/runtime stack.
Instal·la
npx skillscat add assistant-ui/tool-ui/tool-ui Instal·la des del registre de SkillsCat.
Tool UI
Use this skill to move from request to working Tool UI integration quickly.
Prefer assistant-ui when the project has no existing chat UI/runtime. Treat assistant-ui as optional when the app already has a working runtime.
Step 1: Compatibility and Doctor
Read components.json in the user's project and verify:
components.jsonexists.
Step 2: Install Components
Install command from project root
npx shadcn@latest add @tool-ui/<component-id>Multiple components:
npx shadcn@latest add @tool-ui/plan @tool-ui/progress-tracker @tool-ui/approval-cardComplete component catalog
All 25 Tool UI components with install commands:
Progress
| Component | Description | Install |
|---|---|---|
plan |
Step-by-step task workflows with status tracking | npx shadcn@latest add @tool-ui/plan |
progress-tracker |
Real-time status feedback for multi-step operations | npx shadcn@latest add @tool-ui/progress-tracker |
Input
| Component | Description | Install |
|---|---|---|
option-list |
Let users select from multiple choices | npx shadcn@latest add @tool-ui/option-list |
parameter-slider |
Numeric parameter adjustment controls | npx shadcn@latest add @tool-ui/parameter-slider |
preferences-panel |
Compact settings panel for user preferences | npx shadcn@latest add @tool-ui/preferences-panel |
question-flow |
Multi-step guided questions with branching | npx shadcn@latest add @tool-ui/question-flow |
Display
| Component | Description | Install |
|---|---|---|
citation |
Display source references with attribution | npx shadcn@latest add @tool-ui/citation |
item-carousel |
Horizontal carousel for browsing collections | npx shadcn@latest add @tool-ui/item-carousel |
link-preview |
Rich link previews with Open Graph data | npx shadcn@latest add @tool-ui/link-preview |
stats-display |
Key metrics and KPIs in a visual grid | npx shadcn@latest add @tool-ui/stats-display |
terminal |
Show command-line output and logs | npx shadcn@latest add @tool-ui/terminal |
weather-widget |
Weather display with forecasts and conditions | npx shadcn@latest add @tool-ui/weather-widget |
Artifacts
| Component | Description | Install |
|---|---|---|
chart |
Visualize data with interactive charts (needs recharts) |
npx shadcn@latest add @tool-ui/chart |
code-block |
Display syntax-highlighted code snippets | npx shadcn@latest add @tool-ui/code-block |
code-diff |
Compare code changes with syntax-highlighted diffs (needs @pierre/diffs) |
npx shadcn@latest add @tool-ui/code-diff |
data-table |
Present structured data in sortable tables | npx shadcn@latest add @tool-ui/data-table |
message-draft |
Review and approve messages before sending | npx shadcn@latest add @tool-ui/message-draft |
instagram-post |
Render Instagram post previews | npx shadcn@latest add @tool-ui/instagram-post |
linkedin-post |
Render LinkedIn post previews | npx shadcn@latest add @tool-ui/linkedin-post |
x-post |
Render X post previews | npx shadcn@latest add @tool-ui/x-post |
Confirmation
| Component | Description | Install |
|---|---|---|
approval-card |
Binary confirmation for agent actions | npx shadcn@latest add @tool-ui/approval-card |
order-summary |
Display purchases with itemized pricing | npx shadcn@latest add @tool-ui/order-summary |
Media
| Component | Description | Install |
|---|---|---|
audio |
Audio playback with artwork and metadata | npx shadcn@latest add @tool-ui/audio |
image |
Display images with metadata and attribution | npx shadcn@latest add @tool-ui/image |
image-gallery |
Masonry grid with fullscreen lightbox viewer | npx shadcn@latest add @tool-ui/image-gallery |
video |
Video playback with controls and poster | npx shadcn@latest add @tool-ui/video |
Example installs by use case
# Planning flow (plan + progress + approval)
npx shadcn@latest add @tool-ui/plan @tool-ui/progress-tracker @tool-ui/approval-card
# Research output (citation + link preview + code)
npx shadcn@latest add @tool-ui/citation @tool-ui/link-preview @tool-ui/code-block @tool-ui/code-diff
# Data display (table + chart + stats)
npx shadcn@latest add @tool-ui/data-table @tool-ui/chart @tool-ui/stats-display
# npm i recharts # peer for chart
# Media (images + video + audio)
npx shadcn@latest add @tool-ui/image @tool-ui/image-gallery @tool-ui/video @tool-ui/audioToolkit setup in a codebase
After installing components, wire them into assistant-ui via a Toolkit. This section covers the full setup: provider, runtime, toolkit file, and ID handling.
1. Provider and runtime
Create an assistant wrapper that provides runtime, transport, and tools:
"use client";
import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
import { AssistantRuntimeProvider, Tools, useAui } from "@assistant-ui/react";
import {
AssistantChatTransport,
useChatRuntime,
} from "@assistant-ui/react-ai-sdk";
import { Thread } from "@/components/assistant-ui/thread";
import { toolkit } from "@/components/toolkit";
export const Assistant = () => {
const runtime = useChatRuntime({
transport: new AssistantChatTransport({ api: "/api/chat" }),
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
});
const aui = useAui({ tools: Tools({ toolkit }) });
return (
<AssistantRuntimeProvider runtime={runtime} aui={aui}>
<div className="h-dvh">
<Thread />
</div>
</AssistantRuntimeProvider>
);
};Key points:
useChatRuntime+AssistantChatTransport: connects to your chat API.Tools({ toolkit }): forwards tool definitions and renderers to the model.sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls: auto-continues after tool calls (optional but common for tool-heavy flows).
2. Toolkit file structure
Create a single toolkit.ts (or toolkit.tsx) that exports a Toolkit object. Each key is a tool name; each value has type, description, parameters, and render.
-Frontend vs backend tools
| - | Frontend | Backend | |
|---|---|---|---|
| - | Implementation | Runs in the browser; user interaction commits via addResult |
Tool implementation lives on the server; model returns the result |
| - | execute |
Required — runs the tool UI flow client-side | Not needed |
| - | parameters |
Required (schema for model args) | does not use, uses inputSchema instead if backend llm is done via aisdk |
| - | render |
Required (UI for args, status, result, addResult) |
Required (UI for result) |
Backend tools (model returns result; no user input):
import { type Toolkit } from "@assistant-ui/react";
import { Plan } from "@/components/tool-ui/plan";
import { safeParseSerializablePlan } from "@/components/tool-ui/plan/schema";
export const toolkit: Toolkit = {
showPlan: {
type: "backend",
render: ({ result }) => {
const parsed = safeParseSerializablePlan(result);
if (!parsed) return null;
return <Plan {...parsed} />;
},
},
};
Frontend tools (model sends args; user interaction commits via addResult):
import { type Toolkit } from "@assistant-ui/react";
import { OptionList } from "@/components/tool-ui/option-list";
import {
SerializableOptionListSchema,
safeParseSerializableOptionList,
} from "@/components/tool-ui/option-list/schema";
const optionListTool: Toolkit[string] = {
description: "Render selectable options with confirm and clear actions.",
parameters: SerializableOptionListSchema,
render: ({ args, toolCallId, result, addResult }) => {
const parsed = safeParseSerializableOptionList({
...args,
id: args?.id ?? `option-list-${toolCallId}`,
});
if (!parsed) return null;
if (result) {
return <OptionList {...parsed} choice={result} />;
}
return (
<OptionList
{...parsed}
onAction={async (actionId, selection) => {
if (actionId === "confirm" || actionId === "cancel") {
await addResult?.(selection);
}
}}
/>
);
},
};
export const toolkit: Toolkit = {
option_list: optionListTool,
approval_card: {
/* ... */
},
};3. API route (AI SDK)
When the chat API uses the AI SDK (streamText), define backend tools with tool() from ai:
- Use
inputSchema - Backend tools use
executeon the server; the result is streamed and rendered via the toolkitrenderfunction
import { streamText, tool, convertToModelMessages } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
// With frontend tools: ...frontendTools(clientTools) — clientTools come from the request body via AssistantChatTransport
const result = streamText({
model: openai("gpt-4o"),
messages: await convertToModelMessages(messages),
tools: {
get_weather: tool({
description:
"Get the current weather and forecast for a location. Returns data to display in a weather widget.",
inputSchema: z.object({
location: z.string().describe("City name, e.g. 'San Francisco'"),
units: z
.enum(["celsius", "fahrenheit"])
.default("fahrenheit")
.describe("Temperature unit"),
}),
execute: async ({ location, units }) => {
// Fetch weather data, return shape matching your widget schema
return { location, units, /* ... */ };
},
}),
},
});4. Action-centric vs compound components
| Pattern | Components | Usage |
|---|---|---|
| Action-centric | OptionList, ParameterSlider, PreferencesPanel, ApprovalCard |
Wire onAction or onConfirm/onCancel directly; no ToolUI wrapper. Pass choice={result} for receipt state. |
| Compound | OrderSummary, DataTable, etc. |
Wrap in ToolUI + ToolUI.Surface + ToolUI.Actions; use DecisionActions or LocalActions. |
Action-centric example (OptionList):
return (
<OptionList
{...parsed}
onAction={async (actionId, selection) => {
if (actionId === "confirm" || actionId === "cancel") {
await addResult?.(selection);
}
}}
/>
);Compound example (OrderSummary with DecisionActions):
return (
<ToolUI id={parsed.id}>
<ToolUI.Surface>
<OrderSummary {...parsed} />
</ToolUI.Surface>
<ToolUI.Actions>
<ToolUI.DecisionActions
actions={[
{ id: "cancel", label: "Cancel", variant: "outline" },
{ id: "confirm", label: "Purchase" },
]}
onAction={(action) =>
createDecisionResult({ decisionId: parsed.id, action })
}
onCommit={(decision) => addResult?.(decision)}
/>
</ToolUI.Actions>
</ToolUI>
);ApprovalCard uses embedded actions; wire onConfirm/onCancel directly:
return (
<ApprovalCard
{...parsed}
choice={
result === "approved" || result === "denied" ? result : parsed.choice
}
onConfirm={async () => addResult?.("approved")}
onCancel={async () => addResult?.("denied")}
/>
);Action Model
Tool UI uses two action surfaces, rendered as compound siblings outside the display component:
ToolUI.LocalActions: non-consequential side effects (export, copy, open link). Handlers must not calladdResult(...).ToolUI.DecisionActions: consequential choices that produce aDecisionResultenvelope viacreateDecisionResult(...). The commit callback callsaddResult(...).
Compound wrapper pattern for display components with actions:
<ToolUI id={surfaceId}>
<ToolUI.Surface>
<DataTable {...props} />
</ToolUI.Surface>
<ToolUI.Actions>
<ToolUI.LocalActions
actions={[{ id: "export-csv", label: "Export CSV" }]}
onAction={(actionId) => {
/* side effects only */
}}
/>
</ToolUI.Actions>
</ToolUI>Three components are action-centric exceptions — they keep embedded action props instead of sibling surfaces. All three share a unified interface:
actions: action buttons rendered by the component.onAction(actionId, state): runs after the action and receives post-action state.onBeforeAction(actionId, state): guard evaluated before an action runs.
| Component | State type passed to handlers |
|---|---|
OptionList |
OptionListSelection |
ParameterSlider |
SliderValue[] |
PreferencesPanel |
PreferencesValue |
Components using the compound pattern: CodeBlock, CodeDiff, Terminal, ProgressTracker.
Context is shared via createContext + use() (React 19). Subcomponents throw if used outside their Root.
Receipt and Choice Convention
Components with outcomes use a choice prop to render confirmed/completed state:
| Component | choice type |
Values / shape |
|---|---|---|
ApprovalCard |
"approved" | "denied" |
String literal |
OptionList |
string | string[] |
Selected option ID(s) |
OrderSummary |
OrderDecision |
{ action: "confirm", orderId?, confirmedAt? } |
ProgressTracker |
ToolUIReceipt |
{ outcome, summary, identifiers?, at } (shared type) |
When choice is present, the component renders in receipt mode — read-only, no actions.
Operational Rules
- Install the smallest set of components that solves the request.
Notes:
Frontend tools need an execute function
Backend tools have the tool implementation on the server side.
Backend tool don't need either
Ignore the generated files