+
+```
+
+```ts [index.ts] height=260 collapse
+export { default as Tool } from './Tool.vue'
+export { default as ToolContent } from './ToolContent.vue'
+export { default as ToolHeader } from './ToolHeader.vue'
+export { default as ToolInput } from './ToolInput.vue'
+export { default as ToolOutput } from './ToolOutput.vue'
+```
+:::
+
+## Usage with AI SDK
+
+Build a simple stateful weather app that renders the last message in a tool using [useChat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat).
+
+Add the following component to your frontend:
+
+```vue [pages/index.vue] height=260 collapse
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Add the following route to your backend:
+
+```ts [server/api/agent.ts] height=260 collapse
+import { convertToModelMessages, streamText, UIMessage } from 'ai'
+import { defineEventHandler, readBody } from 'h3'
+import { z } from 'zod'
+
+// Allow streaming responses up to 30 seconds
+export const maxDuration = 30
+
+export default defineEventHandler(async (event) => {
+ const body = await readBody(event) as { messages: UIMessage[] }
+ const { messages } = body
+
+ const result = streamText({
+ model: 'openai/gpt-4o',
+ messages: 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.
+
+:::ComponentLoader{label="Preview" componentName="ToolInputStreaming"}
+:::
+
+### Input Available (Running)
+
+Shows a tool that's actively executing with its parameters.
+
+:::ComponentLoader{label="Preview" componentName="ToolInputAvailable"}
+:::
+
+### Input Streaming (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.
+
+:::ComponentLoader{label="Preview" componentName="ToolOutputAvailable"}
+:::
+
+### Output Error
+
+Shows a tool that encountered an error during execution. Opens by default to display the error.
+
+:::ComponentLoader{label="Preview" componentName="ToolOutputError"}
+:::
+
+## Props
+
+### ``
+
+:::field-group
+ ::field{name="class" type="string"}
+ Additional CSS classes to apply to the component.
+ ::
+:::
+
+### ``
+
+:::field-group
+ ::field{name="type" type="ToolUIPart['type']"}
+ The type/name of the tool.
+ ::
+
+ ::field{name="state" type="ToolUIPart['state']"}
+ The current state of the tool (input-streaming, input-available, output-available, or output-error).
+ ::
+
+ ::field{name="title" type="string"}
+ The title of the task.
+ ::
+
+ ::field{name="class" type="string"}
+ Additional CSS classes to apply to the component.
+ ::
+:::
+
+### ``
+
+:::field-group
+ ::field{name="class" type="string"}
+ Additional CSS classes to apply to the component.
+ ::
+:::
+
+### ``
+
+:::field-group
+ ::field{name="input" type="ToolUIPart['input']"}
+ The input parameters passed to the tool, displayed as formatted JSON.
+ ::
+
+ ::field{name="class" type="string"}
+ Additional CSS classes to apply to the component.
+ ::
+:::
+
+### ``
+
+:::field-group
+ ::field{name="output" type="ToolUIPart['output']"}
+ The output/result of the tool execution.
+ ::
+
+ ::field{name="errorText" type="ToolUIPart['errorText']"}
+ An error message if the tool execution failed.
+ ::
+
+ ::field{name="class" type="string"}
+ Additional CSS classes to apply to the component.
+ ::
+:::
diff --git a/apps/www/plugins/ai-elements.ts b/apps/www/plugins/ai-elements.ts
index ecc51da..f28cbb7 100644
--- a/apps/www/plugins/ai-elements.ts
+++ b/apps/www/plugins/ai-elements.ts
@@ -36,6 +36,11 @@ import {
Suggestion,
SuggestionAiInput,
Task,
+ Tool,
+ ToolInputAvailable,
+ ToolInputStreaming,
+ ToolOutputAvailable,
+ ToolOutputError,
Workflow,
} from '@repo/examples'
@@ -80,6 +85,11 @@ export default defineNuxtPlugin((nuxtApp) => {
vueApp.component('CodeBlockDark', CodeBlockDark)
vueApp.component('Checkpoint', Checkpoint)
vueApp.component('Workflow', Workflow)
+ vueApp.component('Tool', Tool)
+ vueApp.component('ToolInputStreaming', ToolInputStreaming)
+ vueApp.component('ToolInputAvailable', ToolInputAvailable)
+ vueApp.component('ToolOutputAvailable', ToolOutputAvailable)
+ vueApp.component('ToolOutputError', ToolOutputError)
vueApp.component('ModelSelector', ModelSelector)
vueApp.component('Context', Context)
vueApp.component('Confirmation', Confirmation)
diff --git a/packages/elements/src/index.ts b/packages/elements/src/index.ts
index a570bc5..5384f73 100644
--- a/packages/elements/src/index.ts
+++ b/packages/elements/src/index.ts
@@ -21,3 +21,4 @@ export * from './shimmer'
export * from './sources'
export * from './suggestion'
export * from './task'
+export * from './tool'
diff --git a/packages/elements/src/tool/Tool.vue b/packages/elements/src/tool/Tool.vue
new file mode 100644
index 0000000..5dd4800
--- /dev/null
+++ b/packages/elements/src/tool/Tool.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/packages/elements/src/tool/ToolContent.vue b/packages/elements/src/tool/ToolContent.vue
new file mode 100644
index 0000000..ae2d817
--- /dev/null
+++ b/packages/elements/src/tool/ToolContent.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/packages/elements/src/tool/ToolHeader.vue b/packages/elements/src/tool/ToolHeader.vue
new file mode 100644
index 0000000..1dc65b6
--- /dev/null
+++ b/packages/elements/src/tool/ToolHeader.vue
@@ -0,0 +1,38 @@
+
+
+
+
+