Skip to main content
Gremorie

Confirmation

Human-in-the-loop approval prompt for tool calls, with request, approved and rejected states driven by tool state.

Overview

Confirmation blocks a tool call until the user approves or rejects it. It wraps an Alert and exposes three slot components (ConfirmationRequest, ConfirmationAccepted, ConfirmationRejected) that auto-show or hide based on the AI SDK ToolUIPart state and the approval verdict.

Use it for irreversible operations (deletes, payments, deploys, write-to-prod) where the user needs to confirm before the tool executes.

Preview

Requesting approval

Approved

Rejected

Installation

bash npx gremorie@latest add rx-confirmation

bash pnpm dlx gremorie@latest add rx-confirmation

bash yarn dlx gremorie@latest add rx-confirmation

bash bunx --bun gremorie@latest add rx-confirmation

Usage

import {
  Confirmation,
  ConfirmationTitle,
  ConfirmationRequest,
  ConfirmationAccepted,
  ConfirmationRejected,
  ConfirmationActions,
  ConfirmationAction,
} from "@gremorie/rx-ai";

export function Example({ state, approval, approve, reject }) {
  return (
    <Confirmation state={state} approval={approval}>
      <ConfirmationTitle>Run delete_records on production?</ConfirmationTitle>
      <ConfirmationRequest>
        <ConfirmationActions>
          <ConfirmationAction variant="outline" onClick={reject}>
            Reject
          </ConfirmationAction>
          <ConfirmationAction onClick={approve}>Approve</ConfirmationAction>
        </ConfirmationActions>
      </ConfirmationRequest>
      <ConfirmationAccepted>Tool executed.</ConfirmationAccepted>
      <ConfirmationRejected>Tool blocked.</ConfirmationRejected>
    </Confirmation>
  );
}

Angular edition planned for Phase 5h. Star the repo to track progress.

API

<Confirmation>

PropTypeDefaultDescription
stateToolUIPart["state"]-Required tool call state from the AI SDK. The component returns null while state is input-streaming or input-available.
approval{ id: string; approved?: boolean; reason?: string }-Required approval payload. Drives which slot is rendered.

Extends ComponentProps<typeof Alert>.

<ConfirmationTitle>

Renders an inline AlertDescription. Use it for the prompt copy.

<ConfirmationRequest> / <ConfirmationAccepted> / <ConfirmationRejected>

ComponentRenders when
ConfirmationRequeststate === "approval-requested"
ConfirmationAcceptedapproval.approved === true and state is approval-responded, output-denied or output-available
ConfirmationRejectedapproval.approved === false and state is approval-responded, output-denied or output-available

Each accepts children: ReactNode.

<ConfirmationActions> / <ConfirmationAction>

PropTypeDefaultDescription
childrenReactNode-Action buttons. The container only renders during approval-requested.

ConfirmationAction extends Button props with a smaller default size (h-8 px-3 text-sm).

useConfirmation()

Hook that returns { approval, state }. Throws when used outside <Confirmation>. Useful for custom slots.

Composition

  1. <Confirmation> wraps an Alert and provides the state context.
  2. <ConfirmationTitle> is the headline question.
  3. <ConfirmationRequest> shows only while approval is pending. Place actions inside.
  4. <ConfirmationActions> + <ConfirmationAction> are the Approve / Reject buttons.
  5. <ConfirmationAccepted> / <ConfirmationRejected> show the resolved outcome.

Variations

Three-state alert

Render every slot so the same JSX serves the full lifecycle.

<Confirmation state={state} approval={approval}>
  <ConfirmationTitle>Charge $499 to the saved card?</ConfirmationTitle>
  <ConfirmationRequest>
    <ConfirmationActions>
      <ConfirmationAction variant="outline" onClick={reject}>
        Cancel
      </ConfirmationAction>
      <ConfirmationAction onClick={approve}>Charge</ConfirmationAction>
    </ConfirmationActions>
  </ConfirmationRequest>
  <ConfirmationAccepted>Card charged successfully.</ConfirmationAccepted>
  <ConfirmationRejected>Charge cancelled.</ConfirmationRejected>
</Confirmation>

Destructive variant

Use Alert variants and a destructive action button to signal severity.

<Confirmation state={state} approval={approval} variant="destructive">
  <ConfirmationTitle>Permanently delete 1,204 records?</ConfirmationTitle>
  <ConfirmationRequest>
    <ConfirmationActions>
      <ConfirmationAction variant="outline" onClick={reject}>
        Keep them
      </ConfirmationAction>
      <ConfirmationAction variant="destructive" onClick={approve}>
        Delete
      </ConfirmationAction>
    </ConfirmationActions>
  </ConfirmationRequest>
</Confirmation>

Approval with reason

Surface the rejection reason captured on approval.reason.

<Confirmation state={state} approval={approval}>
  <ConfirmationTitle>Deploy to production?</ConfirmationTitle>
  <ConfirmationRejected>
    <p>Deploy blocked. Reason: {approval?.reason ?? 'no reason given'}.</p>
  </ConfirmationRejected>
</Confirmation>

Accessibility

  • Keyboard: action buttons participate in tab order and respond to Enter / Space.
  • ARIA: the underlying Alert exposes role="alert" so screen readers announce the prompt when it appears.
  • Screen readers: each slot uses semantic text, so the request, accepted and rejected copy read sequentially as the state transitions.
  • Focus management: when the prompt mounts, move focus programmatically to the primary action so keyboard users can confirm without hunting for it.
  • Tool - the tool-call surface this primitive guards
  • Plan - when the approval is for a multi-step plan rather than a single tool call
  • Message - the conversation surface that hosts the confirmation

On this page