# Tool Approval

Tool Approval [#tool-approval]

The **ToolApproval** primitive is a human-in-the-loop confirmation card that gates a tool call before it executes. It shows the pending tool + args with approve/reject buttons, then re-renders read-only as an immutable "receipt" of the decision. It pairs with the `Tool` primitive and feeds the choice back into the agent loop via callbacks. Data source: `useAgent` (ReAct tool calls).

Preview [#preview]

```tsx
'use client';

/**
 * @file tool-approval-demo.tsx
 * @description Docs preview for `ToolApproval`. Approve or reject a pending tool
 * call; the card re-renders as a read-only receipt of the decision.
 */
import * as React from 'react';
import { ToolApproval, type ApprovalDecision } from '@/components/tool-approval';

export default function ToolApprovalDemo() {
  const [decision, setDecision] = React.useState<ApprovalDecision | null>(null);

  return (
    <div className="flex w-full max-w-lg flex-col gap-3">
      <ToolApproval
        toolName="send_email"
        args={{ to: 'team@example.com', subject: 'Weekly summary' }}
        decision={decision}
        onApprove={() => setDecision('approved')}
        onReject={() => setDecision('rejected')}
      />
      {decision && (
        <button
          type="button"
          onClick={() => setDecision(null)}
          className="self-start text-xs text-muted-foreground underline"
        >
          Reset demo
        </button>
      )}
    </div>
  );
}
```

Installation [#installation]

```bash
npx shadcn@latest add @localmode/ui/conversation/tool-approval
```

Data source & dependencies [#data-source--dependencies]

**Data source:** renders the pending call and emits the approve/reject decision — works with any backend. Recommended producer: resume the `useAgent` loop on the decision (optionally logging to the audit log) via `@localmode/react` (on-device, optional).

* `clsx` + `tailwind-merge` — via the shared `cn()` util (installed automatically as a registry dependency)

Files installed [#files-installed]

* `tool-approval.tsx` — `ToolApproval`
* `lib/utils.ts` — the `cn()` helper (if not already present)

Props [#props]

**ToolApproval**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `toolName` | `string` | — | **Required.** The tool name awaiting approval. |
| `args` | `Record<string, unknown>` | — | The proposed arguments. |
| `decision` | `"rejected" \| "approved" \| null` | — | A controlled decision; when set, the card renders read-only as a receipt. |
| `onApprove` | `function` | — | Fires when the user approves — proceed with the tool call. |
| `onReject` | `function` | — | Fires when the user rejects — skip/abort the tool call. |

Examples [#examples]

Gate a sensitive tool [#gate-a-sensitive-tool]

```tsx
import { ToolApproval } from '@/components/tool-approval';

<ToolApproval
  toolName="send_email"
  args={pendingCall.args}
  decision={decision}
  onApprove={() => resume('approved')}
  onReject={() => resume('rejected')}
/>
```

Customization [#customization]

Pass a controlled `decision` to render the receipt from persisted state; leave it unset to let the card manage its own approve/reject state. Pair it with the audit-log primitives to record an immutable approval trail.

These primitives are presentational and hook-driven: they render props and emit callbacks, holding only local view state. The orchestration state (e.g. `useAgent`) lives in your app. Every surface uses shadcn/ui CSS-variable utilities, so it inherits your theme — restyle the copied file freely.