# Tool



import ToolDefault from "@/components/nexus-ui/examples/tool/default";
import ToolPending from "@/components/nexus-ui/examples/tool/pending";
import ToolReady from "@/components/nexus-ui/examples/tool/ready";
import ToolRunning from "@/components/nexus-ui/examples/tool/running";
import ToolErrorState from "@/components/nexus-ui/examples/tool/error";

Status-aware UI for rendering tool calls (input and output JSON) in agent/chat interfaces. `Tool` is provider-agnostic and works well with Vercel AI SDK tool parts by mapping runtime tool states to the component status model.

<DemoWithCode src="components/nexus-ui/examples/tool/default.tsx" previewClassName="max-h-none! h-auto min-h-[420px] flex flex-col items-center py-30! justify-start">
  <ToolDefault />
</DemoWithCode>

Installation [#installation]

<Tabs items={["CLI", "Manual"]} framed={false}>
  <Tab value="CLI">
    <Tabs items={["npm", "pnpm", "yarn", "bun"]}>
      <Tab value="npm">
        ```bash
        npx shadcn@latest add @nexus-ui/tool
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm dlx shadcn@latest add @nexus-ui/tool
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn dlx shadcn@latest add @nexus-ui/tool
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bunx shadcn@latest add @nexus-ui/tool
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab value="Manual">
    <Steps>
      <Step>
        <h3>
          Install the following dependencies:
        </h3>

        <Tabs items={["npm", "pnpm", "yarn", "bun"]}>
          <Tab value="npm">
            ```bash
            npx shadcn@latest add badge collapsible && npm install @hugeicons/react @hugeicons/core-free-icons @react-symbols/icons shiki
            ```
          </Tab>

          <Tab value="pnpm">
            ```bash
            pnpm dlx shadcn@latest add badge collapsible && pnpm add @hugeicons/react @hugeicons/core-free-icons @react-symbols/icons shiki
            ```
          </Tab>

          <Tab value="yarn">
            ```bash
            yarn dlx shadcn@latest add badge collapsible && yarn add @hugeicons/react @hugeicons/core-free-icons @react-symbols/icons shiki
            ```
          </Tab>

          <Tab value="bun">
            ```bash
            bunx shadcn@latest add badge collapsible && bun add @hugeicons/react @hugeicons/core-free-icons @react-symbols/icons shiki
            ```
          </Tab>
        </Tabs>
      </Step>

      <Step>
        <h3>
          Copy the following files into your project.
        </h3>

        <ComponentSource src="components/nexus-ui/tool.tsx" title="components/nexus-ui/tool.tsx" />

        <ComponentSource src="components/nexus-ui/codeblock-new.tsx" title="components/nexus-ui/codeblock-new.tsx" />

        <ComponentSource src="lib/shiki/highlighter.ts" title="lib/shiki/highlighter.ts" />

        <ComponentSource src="app/shiki.css" title="app/shiki.css" />
      </Step>

      <Step>
        <h3>
          Ensure your Shiki CSS is loaded.
        </h3>

        `CodeblockShiki` expects `.shiki` styles. Import `app/shiki.css` once at your app root (for example in `app/layout.tsx`):

        ```tsx
        import "./global.css";
        import "./shiki.css";
        ```
      </Step>

      <Step>
        <h3>
          Update import paths to match your project setup.
        </h3>
      </Step>
    </Steps>
  </Tab>
</Tabs>

Usage [#usage]

```tsx keepBackground
import {
  Tool,
  ToolTrigger,
  ToolContent,
  ToolInput,
  ToolOutput,
  type ToolStatus,
} from "@/components/nexus-ui/tool";
```

```tsx keepBackground noCollapse
<Tool status="completed" defaultOpen>
  <ToolTrigger name="get_weather" />
  <ToolContent>
    <ToolInput payload={{ city: "Paris", unit: "celsius" }} />
    <ToolOutput
      payload={{ city: "Paris", temperature: 22, condition: "sunny" }}
    />
  </ToolContent>
</Tool>
```

Examples [#examples]

Pending [#pending]

Set `status="pending"` and render only `ToolTrigger` + `ToolContent` while arguments are still streaming.

<DemoWithCode src="components/nexus-ui/examples/tool/pending.tsx" previewClassName="max-h-none! h-auto min-h-[420px] flex flex-col items-center py-30! justify-start">
  <ToolPending />
</DemoWithCode>

Ready [#ready]

Set `status="ready"`, render `ToolInput` with parsed arguments, and omit `ToolOutput` until execution starts.

<DemoWithCode src="components/nexus-ui/examples/tool/ready.tsx" previewClassName="max-h-none! h-auto min-h-[420px] flex flex-col items-center py-30! justify-start">
  <ToolReady />
</DemoWithCode>

Running [#running]

Set `status="running"` and keep only `ToolInput` visible while the tool call is in progress.

<DemoWithCode src="components/nexus-ui/examples/tool/running.tsx" previewClassName="max-h-none! h-auto min-h-[420px] flex flex-col items-center py-30! justify-start">
  <ToolRunning />
</DemoWithCode>

Error State [#error-state]

Set `status="error"`, keep `ToolInput` visible, and render `ToolOutput` with `errorText` (and `showWhen={["error"]}`) to show a destructive error message.

<DemoWithCode src="components/nexus-ui/examples/tool/error.tsx" previewClassName="max-h-none! h-auto min-h-[420px] flex flex-col items-center py-30! justify-start">
  <ToolErrorState />
</DemoWithCode>

Vercel AI SDK Integration [#vercel-ai-sdk-integration]

`Tool` maps cleanly to AI SDK tool parts by converting streamed tool part state into `ToolStatus` and passing `input` / `output` payloads directly.

Use it with [Vercel AI SDK](https://sdk.vercel.ai) by:

* mapping AI SDK tool part states (`input-streaming`, `input-available`, `output-available`, `output-error`) into your UI states (`pending`, `ready`, `running`, `completed`, `error`)
* rendering `part.input` in `ToolInput`
* rendering `part.output` in `ToolOutput` when available
* passing `errorText` to `ToolOutput` for `error` state

`ready` is an app-level state in this mapping (for example, waiting on user approval before execution). AI SDK does not emit `ready` as a native tool part state.

`pending` via `input-streaming` is only available in streaming flows (for example, `streamText` / streamed UI messages).

<Steps>
  <Step>
    <h3>
      Install the AI SDK
    </h3>

    ```bash
    npm install ai @ai-sdk/react
    ```
  </Step>

  <Step>
    <h3>
      Stream messages from your chat API route
    </h3>

    ```ts title="app/api/chat/route.ts"
    import { streamText, convertToModelMessages, tool, type UIMessage } from "ai";
    import { z } from "zod";

    export async function POST(req: Request) {
      const { messages }: { messages: UIMessage[] } = await req.json();

      const result = streamText({
        model: "anthropic/claude-sonnet-4.5",
        messages: await convertToModelMessages(messages),
        tools: {
          displayWeather: tool({
            description: "Get weather for a city",
            inputSchema: z.object({
              city: z.string(),
              unit: z.enum(["celsius", "fahrenheit"]),
            }),
            execute: async ({ city, unit }) => ({
              city,
              unit,
              temperature: 22,
              condition: "sunny",
            }),
          }),
        },
      });

      return result.toUIMessageStreamResponse();
    }
    ```
  </Step>

  <Step>
    <h3>
      Map assistant tool parts into 

      <code className="tracking-0!">Tool</code>
    </h3>

    ```tsx
    "use client";

    import { useChat } from "@ai-sdk/react";
    import { DefaultChatTransport, type UIMessage } from "ai";
    import {
      Tool,
      ToolTrigger,
      ToolContent,
      ToolInput,
      ToolOutput,
      type ToolStatus,
    } from "@/components/nexus-ui/tool";

    type ToolPartLike = {
      type: string;
      state?: string;
      input?: unknown;
      output?: unknown;
      errorText?: string;
      error?: unknown;
    };

    function mapToolStatus(
      part: ToolPartLike,
      isAwaitingApproval: boolean,
    ): ToolStatus | null {
      switch (part.state) {
        case "input-streaming":
          // Streaming-only: arguments are still being generated.
          return "pending";
        case "input-available":
          // App-level mapping: use "ready" while waiting for approval/user action.
          return isAwaitingApproval ? "ready" : "running";
        case "output-available":
          return "completed";
        case "output-error":
          return "error";
        default:
          return null;
      }
    }

    function toolNameFromPartType(type: string): string {
      return type.startsWith("tool-") ? type.slice(5) : type;
    }

    export default function ToolCallsFromUseChat() {
      const { messages } = useChat({
        transport: new DefaultChatTransport({ api: "/api/chat" }),
      });

      const assistant = [...messages].reverse().find((m) => m.role === "assistant");
      if (!assistant) return null;

      const toolParts = assistant.parts.filter(
        (part): part is ToolPartLike => part.type.startsWith("tool-"),
      );

      if (toolParts.length === 0) return null;

      return (
        <div className="space-y-3">
          {toolParts.map((part, index) => {
            const status = mapToolStatus(part, false);
            if (!status) return null;

            return (
              <Tool key={`${part.type}-${index}`} status={status} defaultOpen>
                <ToolTrigger name={toolNameFromPartType(part.type)} />
                <ToolContent>
                  <ToolInput payload={part.input} />
                  <ToolOutput
                    payload={part.output}
                    errorText={part.errorText}
                    showWhen={["completed", "error"]}
                  />
                </ToolContent>
              </Tool>
            );
          })}
        </div>
      );
    }
    ```
  </Step>
</Steps>

API Reference [#api-reference]

Tool [#tool]

Root wrapper for a single tool call. Provides status context and status color tokens for child components. Wraps [Collapsible Root](https://www.radix-ui.com/primitives/docs/components/collapsible#root).

<TypeTable
  type={{
  status: {
    type: '"pending" | "ready" | "running" | "completed" | "error"',
    description:
      "Tool lifecycle state. Drives icon, badge label, and status colors used by descendants.",
  },
  open: {
    type: "boolean",
    description: "Controlled open state for the collapsible container.",
  },
  defaultOpen: {
    type: "boolean",
    description: "Initial open state in uncontrolled mode.",
  },
  onOpenChange: {
    type: "(open: boolean) => void",
    description: "Called when open state changes.",
  },
  className: {
    type: "string",
    description: "Additional classes merged with the root container styles.",
  },
}}
/>

ToolTrigger [#tooltrigger]

Header row for tool name, status icon, status badge, and expand/collapse chevron. Wraps [Collapsible Trigger](https://www.radix-ui.com/primitives/docs/components/collapsible#trigger).

<TypeTable
  type={{
  name: {
    type: "string",
    description: "Tool name shown in the trigger (for example, get_weather).",
  },
  className: {
    type: "string",
    description: "Additional classes merged with trigger row styles.",
  },
}}
/>

ToolContent [#toolcontent]

Body wrapper for input/output/error sections. Wraps [Collapsible Content](https://www.radix-ui.com/primitives/docs/components/collapsible#content).

<TypeTable
  type={{
  className: {
    type: "string",
    description: "Additional classes for spacing/layout inside expanded content.",
  },
  children: {
    type: "React.ReactNode",
    description: "Typically ToolInput and ToolOutput.",
  },
}}
/>

ToolInput [#toolinput]

Renders the tool input payload in a JSON codeblock.

<TypeTable
  type={{
  payload: {
    type: "unknown",
    description:
      "Input payload. Objects are pretty-stringified; strings are rendered as-is.",
  },
}}
/>

ToolOutput [#tooloutput]

Renders tool output payload in a JSON codeblock when the current tool state matches `showWhen`.

<TypeTable
  type={{
  payload: {
    type: "unknown",
    description:
      "Output payload. Objects are pretty-stringified; strings are rendered as-is.",
  },
  showWhen: {
    type: 'Array<"pending" | "ready" | "running" | "completed" | "error">',
    default: '["completed"]',
    description:
      "Controls which tool states render the output section.",
  },
  errorText: {
    type: "string",
    description:
      "Error message shown as a destructive text block when tool state is error.",
  },
}}
/>
