AI Interface Components

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.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

220
0
Active edited usageDefault props
<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.json

Or, 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

PropTypeDefaultDescription
toolNamestring—Tool name (e.g. "Write file")
toolIconReactNodeWrench iconTool icon
descriptionstring—One sentence describing what the agent wants to do
paramsToolPermissionParam[][]Parameter summary; folded by default beyond 4 rows
risk"low" | "medium" | "high""medium"Risk level; high shows a red stripe and hides always-allow
statusToolPermissionStatus—Controlled status; managed internally when omitted (uncontrolled)
defaultStatusToolPermissionStatus"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
allowLabelstring"Allow"Allow button label (Chinese build: "允許")
denyLabelstring"Deny"Deny button label (Chinese build: "拒絕")
alwaysLabelstring"Always allow this tool"Always-allow button label (Chinese build: "永遠允許此工具")
hideAlwaysbooleanfalseHide the always-allow button
expiresInnumber0Countdown in seconds; reaching zero counts as a denial, 0 disables it
collapseDurationnumber220Collapse animation duration (ms), also written to --wb-tool-permission-card-duration
autoFocusbooleantrueAuto-focus Allow on appear (Deny when risk is high)
classNamestring—Passed through to the outermost container

ToolPermissionStatus is "pending" \| "allowed" \| "denied" \| "always".

ToolPermissionParam

FieldTypeDefaultDescription
labelstring—Parameter name (e.g. "Path")
valuestring—Parameter value; truncated when long, full text shown on hover
monobooleanfalseRender 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 outer motion.div carries layout, folding into a single line over collapseDuration with 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 onDecision returns a Promise, the pressed button shows a spinner and sets aria-busy while the other buttons pause; if the Promise rejects, the card stays pending so you can try again.
  • The expiresIn countdown is a thin progress bar along the bottom of the card that turns red in its last quarter; at zero it calls onDecision("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" with aria-labelledby pointing 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 an aria-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