• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
drewmcdonald

drewmcdonald

promptCompare

Public
Like
promptCompare
Home
Code
7
.claude
3
backend
2
docs
1
frontend
4
shared
.mcp.json
deno.json
Environment variables
2
Branches
1
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
.claude
/
skills
/
ai-elements
/
references
/
tool.md
Code
/
.claude
/
skills
/
ai-elements
/
references
/
tool.md
Search
…
Viewing readonly version of main branch: v148
View latest version
tool.md

Tool

A collapsible component for displaying tool invocation details in AI chatbot interfaces.

The Tool component displays a collapsible interface for showing/hiding tool details. It is designed to take the ToolUIPart type from the AI SDK and display it in a collapsible interface.

See scripts/tool.tsx for this example.

Installation

npx ai-elements@latest add tool

Usage in AI SDK

Build a simple stateful weather app that renders the last message in a tool using useChat.

Add the following component to your frontend:

Create val
"use client"; import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport, type ToolUIPart } from "ai"; import { Button } from "@/components/ui/button"; import { MessageResponse } from "@/components/ai-elements/message"; import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput, } from "@/components/ai-elements/tool"; type WeatherToolInput = { location: string; units: "celsius" | "fahrenheit"; }; type WeatherToolOutput = { location: string; temperature: string; conditions: string; humidity: string; windSpeed: string; lastUpdated: string; }; type WeatherToolUIPart = ToolUIPart<{ fetch_weather_data: { input: WeatherToolInput; output: WeatherToolOutput; }; }>; const Example = () => { const { messages, sendMessage, status } = useChat({ transport: new DefaultChatTransport({ api: "/api/weather", }), }); const handleWeatherClick = () => { sendMessage({ text: "Get weather data for San Francisco in fahrenheit" }); }; const latestMessage = messages[messages.length - 1]; const weatherTool = latestMessage?.parts?.find( (part) => part.type === "tool-fetch_weather_data", ) as WeatherToolUIPart | undefined; return ( <div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]"> <div className="flex flex-col h-full"> <div className="space-y-4"> <Button onClick={handleWeatherClick} disabled={status !== "ready"}> Get Weather for San Francisco </Button> {weatherTool && ( <Tool defaultOpen={true}> <ToolHeader type="tool-fetch_weather_data" state={weatherTool.state} /> <ToolContent> <ToolInput input={weatherTool.input} /> <ToolOutput output={ <MessageResponse> {formatWeatherResult(weatherTool.output)} </MessageResponse> } errorText={weatherTool.errorText} /> </ToolContent> </Tool> )} </div> </div> </div> ); }; function formatWeatherResult(result: WeatherToolOutput): string { return `**Weather for ${result.location}** **Temperature:** ${result.temperature} **Conditions:** ${result.conditions} **Humidity:** ${result.humidity} **Wind Speed:** ${result.windSpeed} *Last updated: ${result.lastUpdated}*`; } export default Example;

Add the following route to your backend:

Create val
import { convertToModelMessages, streamText, UIMessage } from "ai"; import { z } from "zod"; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "openai/gpt-4o", messages: await convertToModelMessages(messages), tools: { fetch_weather_data: { description: "Fetch weather information for a specific location", parameters: z.object({ location: z .string() .describe("The city or location to get weather for"), units: z .enum(["celsius", "fahrenheit"]) .default("celsius") .describe("Temperature units"), }), inputSchema: z.object({ location: z.string(), units: z.enum(["celsius", "fahrenheit"]).default("celsius"), }), execute: async ({ location, units }) => { await new Promise((resolve) => setTimeout(resolve, 1500)); const temp = units === "celsius" ? Math.floor(Math.random() * 35) + 5 : Math.floor(Math.random() * 63) + 41; return { location, temperature: `${temp}°${units === "celsius" ? "C" : "F"}`, conditions: "Sunny", humidity: `12%`, windSpeed: `35 ${units === "celsius" ? "km/h" : "mph"}`, lastUpdated: new Date().toLocaleString(), }; }, }, }, }); return result.toUIMessageStreamResponse(); }

Features

  • Collapsible interface for showing/hiding tool details
  • Visual status indicators with icons and badges
  • Support for multiple tool execution states (pending, running, completed, error)
  • Formatted parameter display with JSON syntax highlighting
  • Result and error handling with appropriate styling
  • Composable structure for flexible layouts
  • Accessible keyboard navigation and screen reader support
  • Consistent styling that matches your design system
  • Auto-opens completed tools by default for better UX

Examples

Input Streaming (Pending)

Shows a tool in its initial state while parameters are being processed.

See scripts/tool-input-streaming.tsx for this example.

Input Available (Running)

Shows a tool that's actively executing with its parameters.

See scripts/tool-input-available.tsx for this example.

Output Available (Completed)

Shows a completed tool with successful results. Opens by default to show the results. In this instance, the output is a JSON object, so we can use the CodeBlock component to display it.

See scripts/tool-output-available.tsx for this example.

Output Error

Shows a tool that encountered an error during execution. Opens by default to display the error.

See scripts/tool-output-error.tsx for this example.

Props

<Tool />

PropTypeDefaultDescription
...propsReact.ComponentProps<typeof Collapsible>-Any other props are spread to the root Collapsible component.

<ToolHeader />

PropTypeDefaultDescription
titlestring-Custom title to display instead of the derived tool name.
typeToolUIPart[RequiredThe type/name of the tool.
stateToolUIPart[RequiredThe current state of the tool (input-streaming, input-available, output-available, or output-error).
toolNamestring-Required when type is
classNamestring-Additional CSS classes to apply to the header.
...propsReact.ComponentProps<typeof CollapsibleTrigger>-Any other props are spread to the CollapsibleTrigger.

<ToolContent />

PropTypeDefaultDescription
...propsReact.ComponentProps<typeof CollapsibleContent>-Any other props are spread to the CollapsibleContent.

<ToolInput />

PropTypeDefaultDescription
inputToolUIPart[-The input parameters passed to the tool, displayed as formatted JSON.
...propsReact.ComponentProps<-Any other props are spread to the underlying div.

<ToolOutput />

PropTypeDefaultDescription
outputReact.ReactNode-The output/result of the tool execution.
errorTextToolUIPart[-An error message if the tool execution failed.
...propsReact.ComponentProps<-Any other props are spread to the underlying div.

Type Exports

ToolPart

Union type representing both static and dynamic tool UI parts.

Create val
type ToolPart = ToolUIPart | DynamicToolUIPart;

Utilities

getStatusBadge

Returns a Badge component with icon and label based on tool state.

Create val
import { getStatusBadge } from "@/components/ai-elements/tool"; // Returns a Badge with appropriate icon and label const badge = getStatusBadge("output-available");

Supported states:

  • input-streaming - "Pending"
  • input-available - "Running"
  • approval-requested - "Awaiting Approval"
  • approval-responded - "Responded"
  • output-available - "Completed"
  • output-error - "Error"
  • output-denied - "Denied"
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.