Skip to content

Choosing a confirmation

Match how risky an action is to the confirmation that asks for it.

Carbon, GitLab Pajamas, Apple's Human Interface Guidelines and Nielsen Norman Group all sort destructive actions into three tiers. The harder an action is to take back, the more effort its confirmation asks for. A routine action that asks for too much teaches people to confirm without reading.

Describe the action to see the control that fits, try it, and copy its code.

I'm confirming that and affects .

Use ConfirmButton: a second click in place is enough when it can be restored.

tsx
"use client"import { ConfirmButton } from "@/components/ui/sureui/confirm-button"export default function ConfirmButtonClickAgain() {  return (    <ConfirmButton      gesture="click-again"      timeout={3000}      onConfirm={deleteBranch}    >      Delete branch    </ConfirmButton>  )}

npx shadcn@latest add @sureui/confirm-button

RiskActions likeUse
LowArchiving a message, removing an item from a list. Common and easy to reverse.Undo: Confirm Button with undo, Undo Toast or Undoable.
MediumRemoving a member, deleting a branch, revoking a key.A second click or a popover: Confirm Button with gesture="click-again", Confirm Menu Item or Confirm Popover. A hold for dangerous one-offs.
HighDeleting a project, a database or an account. Irreversible, with wide impact.A dialog that lists what goes and asks for the name: Confirm Dialog with phrase, or Type to Confirm.

Do it right away and offer Undo. NN/g and Apple both recommend undo over a confirmation for frequent actions: a question people answer many times a day stops being read, and Undo only costs a click when someone made a mistake.

// Nothing has run yet

  • Confirm Button with undo when the button stays on screen.
  • Undo Toast when the control goes away, like an email archived from its own view.
  • Undoable when a row leaves a list or table.

onConfirm runs when the window closes, so there is nothing to reverse on your server when someone presses Undo.

Ask for a second, deliberate step in place, without a modal.

Press and hold, or activate twice, to confirm

// Nothing has run yet

  • Confirm Button with gesture="click-again" arms on the first click and confirms on the second.
  • Confirm Menu Item does the same in dropdown and context menus. Click again is its default.
  • Confirm Popover when the action needs a sentence of context, like which pull requests will close.
  • gesture="hold" for a dangerous one-off, like revoking a production key, where a double click shouldn't count. Confirm Button, Confirm Menu Item, Confirm Popover and Confirm Dialog all take it.
  • gesture="slide" on touch screens, where dragging to the end is harder to do by accident than a tap. Two taps also confirm, for people who can't drag. Confirm Button, Confirm Popover and Confirm Dialog take it.

For an irreversible action with wide impact, show what will be lost and ask for the name of the thing. Typing the name shows people know which project they are deleting, not only that they meant to click.

// Nothing has run yet

  • Confirm Dialog with phrase and consequences.
  • Type to Confirm inline, like in a settings page's danger zone.
  • Consequences lists what goes with it, with counts and names.
  • wait on Confirm Dialog when there's no name to type: a short countdown before confirm unlocks, so people read before they act.

Keep this tier for the few actions that need it.

When one action hits many things, let the friction grow with the count: undo for a few, a second click for dozens, and the count typed for hundreds. There's nothing extra to install. Pick the control from the count.

// Nothing has run yet

npx shadcn@latest add @sureui/type-to-confirm

Type to Confirm installs Confirm Button with it. The bulk actions block shows the same idea in an issue list.

Tool Approval maps its risk levels onto the same tiers. Deny is one click at every level.

riskApprove withTier
"low"A click, then an undo windowLow
"medium"A second clickMedium
"high"A press and holdMedium, for dangerous one-offs
"critical"The typed phraseHigh
tsx
<ToolApproval part={part} risk="low" onRespond={addToolApprovalResponse} /><ToolApproval  part={part}  risk="critical"  phrase={part.input.name}  onRespond={addToolApprovalResponse}/>
  • Use a verb that says what happens: "Delete project", not "OK" or "Yes". The button should make sense without reading the question above it.
  • Name the thing and the count: "Delete 3 files" or "Delete acme-prod", not "Are you sure?" or "Delete selected".
  • Don't rely on red alone. Color doesn't reach everyone, so variant="destructive" goes with a label that says "Delete", never instead of it.
  • For frequent actions, skip the question and offer undo.
  • An undo window is a time limit under WCAG 2.2.1 Timing Adjustable. It pauses while the pointer or focus is on the control and while the tab is hidden, and undo takes up to 60 seconds. For an action people may need longer to reconsider, keep a way to restore it afterwards, like a trash, as in the file manager block.
  • A hold meets WCAG 2.5.2 Pointer Cancellation because letting go early cancels.
  • Keyboard users hold Space or Enter. Screen reader users can confirm a hold with two activations. See Confirm Button.