assistant-ui

tool-ui

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.

assistant-ui 779 31 Actualitzat fa 8 mesos
GitHub

Instal·la

npx skillscat add assistant-ui/tool-ui/tool-ui

Instal·la des del registre de SkillsCat.

SKILL.md

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.json exists.

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-card

Complete 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/audio

Toolkit 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 execute on the server; the result is streamed and rendered via the toolkit render function
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 call addResult(...).
  • ToolUI.DecisionActions: consequential choices that produce a DecisionResult envelope via createDecisionResult(...). The commit callback calls addResult(...).

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