Tool Permission Card
A permission card for when an agent asks to run a tool: shows the tool name and a parameter summary with allow, deny and always-allow, then collapses into a one-line record once decided.
An authorization card that sits in the message stream right before an agent runs a tool with side effects. It lists the tool name, risk level, a one-sentence description and a parameter summary, with Deny, Always allow this tool and Allow at the bottom. Once a decision is made, the parameters and buttons fade away and the whole card collapses with a layout animation into a single-line record ("Allowed: Write file · src/notes/today.md"); click it to expand and review the parameters again. High-risk tools get a red stripe, hide always-allow and put default focus on Deny; you can also set a countdown that counts as a denial when it runs out. It complements the tool call card, which shows execution status — this one handles authorization before execution.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add tool-permission-card.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ToolPermissionCard/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/tool-permission-card.en.jsonOr, once registries are configured in components.json, install it as @webberui/tool-permission-card.
Usage
import { ToolPermissionCard } from "@/components/ui/tool-permission-card";
<ToolPermissionCard
toolName="Write file"
description="The agent wants to create a new file in the project"
risk="low"
params={[
{ label: "Path", value: "src/notes/today.md", mono: true },
{ label: "Size", value: "1.2 KB" },
]}
onDecision={async (decision) => {
// Send the decision back to your agent runtime; a rejection keeps the card pending
await sendToAgent({ tool: "write_file", decision });
}}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
toolName | string | — | Tool name (e.g. "Write file") |
toolIcon | ReactNode | Wrench icon | Tool icon |
description | string | — | One sentence describing what the agent wants to do |
params | ToolPermissionParam[] | [] | Parameter summary; folded by default beyond 4 rows |
risk | "low" | "medium" | "high" | "medium" | Risk level; high shows a red stripe and hides always-allow |
status | ToolPermissionStatus | — | Controlled status; managed internally when omitted (uncontrolled) |
defaultStatus | ToolPermissionStatus | "pending" | Initial status in uncontrolled mode |
onStatusChange | (status: ToolPermissionStatus) => void | — | Called when the status changes |
onDecision | (decision: "allow" | "deny" | "always") => void | Promise<void> | — | Called on decision; if it returns a Promise the button shows a spinner, the card collapses on resolve and stays pending on reject |
allowLabel | string | "Allow" | Allow button label (Chinese build: "允許") |
denyLabel | string | "Deny" | Deny button label (Chinese build: "拒絕") |
alwaysLabel | string | "Always allow this tool" | Always-allow button label (Chinese build: "永遠允許此工具") |
hideAlways | boolean | false | Hide the always-allow button |
expiresIn | number | 0 | Countdown in seconds; reaching zero counts as a denial, 0 disables it |
collapseDuration | number | 220 | Collapse animation duration (ms), also written to --wb-tool-permission-card-duration |
autoFocus | boolean | true | Auto-focus Allow on appear (Deny when risk is high) |
className | string | — | Passed through to the outermost container |
ToolPermissionStatus is "pending" \| "allowed" \| "denied" \| "always".
ToolPermissionParam
| Field | Type | Default | Description |
|---|---|---|---|
label | string | — | Parameter name (e.g. "Path") |
value | string | — | Parameter value; truncated when long, full text shown on hover |
mono | boolean | false | Render in a monospace font (paths, commands, etc.) |
How it works
- Collapse animation: the moment a decision is made, the parameters and button row exit through
AnimatePresence(opacity and height to zero) while the outermotion.divcarrieslayout, folding into a single line overcollapseDurationwith easeInOut. The result icon (✓ / ✕ / ∞) springs from 0.6× to full size, colored emerald, red or sky by outcome. - The collapsed record is a button; click it to expand the description and parameters. The expanded view is read-only — the decision cannot be changed.
- When
onDecisionreturns a Promise, the pressed button shows a spinner and setsaria-busywhile the other buttons pause; if the Promise rejects, the card stays pending so you can try again. - The
expiresIncountdown is a thin progress bar along the bottom of the card that turns red in its last quarter; at zero it callsonDecision("deny")and collapses to Denied. - Button hover transitions and the expand chevron rotation both use
--wb-tool-permission-card-duration, which you can override globally.
Accessibility
- The card is
role="group"witharia-labelledbypointing at the tool name - While pending, Escape inside the card denies; Enter allows when focus is not on a button (a focused button handles Enter itself), with IME composition protection
- Allow is focused by default, switching to Deny for high risk so a stray keypress costs less; focusing never scrolls the page, and
autoFocus={false}turns it off when several cards share a message stream - After a decision, focus moves to the collapsed record (if focus was inside the card); the record exposes
aria-expanded, and anaria-live="polite"region announces outcomes like "Allowed Write file" - The countdown bar is hidden from assistive tech; the seconds remaining are provided as sr-only text
- When the user has reduce motion enabled, collapsing skips the height and layout animation and the record fades in over 120ms, the result icon does not bounce, and the countdown bar becomes a number
Agent Relay Workspaces
Agent workspaces take turns flipping in with 3D rotation, each running a mock task and handing its output to the next, ending in a combined result.
Agent Task Queue
An agent task queue React component with drag-to-reorder, a breathing pulse on the running item, and finished tasks sliding out into a collapsible Done group.