Skip to content
Kura UI

Approval Prompt

beta

Human-in-the-loop permission card (“Kura wants to run `rm -rf build`”) with risk level, details, Approve/Deny/Always allow, ⌘↵ and Esc, and resolved states.

@kura-ui/react/approval-promptCopy page for agents.mdregistry.json

Low-risk command

minimal

Kura wants to run a command

Low risk
pnpm install
Needs approval

High-risk, every option

Description, details, controlled decision, callbacks, Always allow, custom labels and ⌘↵ / Esc.

full

Kura wants to delete a directory

High risk

Clears stale output before rebuilding. Affects 1,204 files under ./build only.

rm -rf build
  • Working directory: ~/code/kura-ui
  • Requested by step 3 of 5, “Rebuild the docs”
  • Not tracked by git — cannot be restored from history
Needs approval
Try ⌘↵ or Esc

Resolved states

Approved, denied and always-allowed, as they appear in history.

state

Kura wants to run a command

Medium risk
git push origin main
Approved

Kura wants to run a command

High risk
curl -X DELETE https://api.example.com/v1/projects/42
Denied

Kura wants to read a file

Low risk
cat .env.example
Always allowed· won't ask again this session

Approve a file edit

A diff CodeBlock in the details, driving a ToolCall's status.

composition
Edit src/config.ts+2 −1, Waiting for approval
edit_file Waiting for approval

Apply this edit?

Low risk
@@ -1,4 +1,5 @@export const config = {removed: retries: 1,added: retries: 3,added: backoff: "exponential", timeout: 12_000,};
Needs approval

Installation

npx shadcn@latest add https://kuro.abstergo.fyi/r/approval-prompt.json

Copies the source into components/kura/approval-prompt with its dependencies (kura-theme, button, kbd, use-controllable-state, use-hotkey, utils). Tokens come from the kura-theme item.

When to use it

Use it for

  • An agent needs explicit permission before a side effect: running a shell command, editing files, sending email, spending money, calling a destructive API.
  • Show exactly what will run (verbatim command) and how risky it is before the user decides.
  • Offer “Always allow” for a class of actions in this session (persist the rule yourself in onAlwaysAllow).
  • Keep the decision visible in the transcript afterwards (Approved / Denied / Always allowed) without the card changing height.

Reach for something else when

  • Showing a tool call that does not need permission — use tool-call (it also has an awaiting-approval status for compact inline approvals).
  • Generic confirm dialogs unrelated to an agent (deleting a document) — use a dialog.
  • Progress of a multi-step plan — use agent-timeline.

Props

PropTypeDefaultDescription
title*React.ReactNode—What the agent wants, e.g. "Kura wants to run a command". Labels the region.
commandstring—The exact command/action, shown verbatim in mono after a $ prompt (wraps, never truncated).
descriptionReact.ReactNode—Why and what it touches; becomes the region's accessible description.
risk"low" | "medium" | "high""medium"Badge (green/amber/red dot). high also turns Approve into a red “Run anyway” button.
detailsReact.ReactNode—Content behind a “Details” expander: a diff (CodeBlock), arguments, affected files.
decision"pending" | "approved" | "denied" | "always"—Decision (controlled).
defaultDecision"pending" | "approved" | "denied" | "always""pending"Initial decision when uncontrolled (use a resolved value to render history).
onDecisionChange(decision: ApprovalDecision) => void—Called with every decision.
onApprove() => void—Called on Approve (button or ⌘/Ctrl+Enter).
onDeny() => void—Called on Deny (button or Esc).
onAlwaysAllow() => void—Shows an “Always allow” ghost button; called when chosen.
shortcutsbooleantrueBind ⌘/Ctrl+Enter (approve) and Esc (deny) on the document while pending, and show the keys on the buttons. Only one prompt on screen should bind them.
approveLabelstring—Approve label. Default “Approve”, or “Run anyway” for high risk.
denyLabelstring"Deny"Deny label.
alwaysAllowLabelstring"Always allow"Always-allow label.
iconReact.ReactNode—Icon in the header tile. Default a terminal.

All native attributes of the root element, className and ref pass through.

Anatomy

  1. ApprovalPrompt<section> labelled by the title and the command (so stacked prompts have distinct names). `data-decision="pending" | "approved" | "denied" | "always"`, `data-risk`.data-slot="approval-prompt"
  2. TitleHeading; mutes once resolved.data-slot="approval-prompt-title"
  3. RiskRisk badge with a coloured dot and text.data-slot="approval-prompt-risk"
  4. CommandMono command well; the <code> is `approval-prompt-code`.data-slot="approval-prompt-command"
  5. DetailsCollapsible details (grid-rows transition, inert when closed).data-slot="approval-prompt-details"
  6. FooterFixed-height footer holding actions and the resolution, so resolving never shifts layout.data-slot="approval-prompt-footer"
  7. WaitingJade “Needs approval” pill — the view's one accent while pending.data-slot="approval-prompt-waiting"
  8. ActionsAlways allow / Deny (Esc) / Approve (⌘↵); inert once resolved.data-slot="approval-prompt-actions"
  9. Resolutionrole="status" line that cross-fades in: Approved / Denied / Always allowed.data-slot="approval-prompt-resolution"

Accessibility

  • Role: region
  • A <section> labelled by the title and described by the description, so screen-reader users land on the whole request.
  • Buttons expose their shortcuts with aria-keyshortcuts; keycaps are also rendered visually.
  • The resolution is a status region, so the decision is announced; the action row becomes inert and hidden.
  • Risk is conveyed in text (“High risk”), not only colour. No dark patterns: Deny is as prominent as Approve.
  • Reduced motion: The waiting pulse stops and resolved states cross-fade without blur; details open without height travel.
KeysAction
Mod+EnterApprove (while pending, when shortcuts is on)
EscapeDeny (while pending, when shortcuts is on; not while typing in an input)
TabDetails, Always allow, Deny, Approve

Notes for agents

  • Show the command verbatim — never paraphrase what will run.
  • The decision is only UI state: actually gate the side effect on onApprove / onDecisionChange in your agent loop.
  • Render past prompts with `defaultDecision` (or controlled `decision`) set to the stored outcome and `shortcuts={false}`.
  • When several prompts are visible, enable `shortcuts` on the newest pending one only.