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.
Workflow
- •Run compatibility and doctor checks.
- •Find candidate components or bundles.
- •Install selected components.
- •Generate scaffolds and integrate runtime wiring.
- •Validate behavior and troubleshoot failures.
Step 1: Compatibility and Doctor
python scripts/tool_ui_compat.py --project <repo-root> python scripts/tool_ui_compat.py --project <repo-root> --doctor
Auto-fix missing @tool-ui registry entry:
python scripts/tool_ui_compat.py --project <repo-root> --fix
Minimum requirements:
- •
components.jsonexists. - •
components.json.aliases.utilsexists (typically@/lib/utils). - •
components.json.registries["@tool-ui"]equalshttps://tool-ui.com/r/{name}.json.
Step 2: Discover Components
Use intent-first discovery:
python scripts/tool_ui_components.py list python scripts/tool_ui_components.py find "<use case keywords>" python scripts/tool_ui_components.py bundle python scripts/tool_ui_components.py bundle planning-flow
References:
- •
references/components-catalog.md - •
references/recipes.md
Step 3: Install Components
Generate install command:
python scripts/tool_ui_components.py install <component-id> [component-id...]
Default install pattern:
npx shadcn@latest add https://tool-ui.com/r/<component-id>.json
Step 4: Scaffold and Integrate
Generate wiring snippet:
python scripts/tool_ui_scaffold.py --mode assistant-backend --component plan python scripts/tool_ui_scaffold.py --mode assistant-frontend --component option-list python scripts/tool_ui_scaffold.py --mode manual --component stats-display
Use integration patterns for final adaptation:
- •
references/integration-patterns.md
Step 5: Validate and Troubleshoot
After integration:
- •Run typecheck/lint.
- •Trigger a real tool call.
- •Confirm UI renders and interactions complete expected flow.
If broken, use:
- •
references/troubleshooting.md
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 |
ESLint enforces this model:
- •
no-embedded-response-actions— bans legacyresponseActions/onResponseActionprops. - •
no-add-result-in-local-actions— preventsaddResult()inside LocalActions handlers. - •
decision-actions-require-envelope— requirescreateDecisionResult(...)in DecisionActions handlers.
Schema Boundary
Import from colocated entrypoints, not barrel index.tsx:
import { DataTable } from "@/components/tool-ui/data-table/data-table";
import { safeParseSerializableDataTable } from "@/components/tool-ui/data-table/schema";
import { ToolUI, createDecisionResult, type Action } from "@/components/tool-ui/shared";
Operational Rules
- •Install the smallest set of components that solves the request.
- •Validate one component first, then scale to multiple components.
- •Keep payload schemas serializable and explicit.
- •For decision flows, wire
DecisionActionswithcreateDecisionResult(...)and commit viaaddResult(...). - •For display components that need actions, use the compound
ToolUIwrapper withLocalActions.
Maintainer Notes
Keep component metadata synchronized with Tool UI source:
python scripts/sync_components.py
Run script tests:
python -m unittest discover -s tests -p "test_*.py"