Skip to content

Confirm Dialog

An alert dialog that asks before an action runs, with a confirm button, a hold or a typed phrase, and an awaitable confirm().

// Nothing has run yet

npx shadcn@latest add @sureui/confirm-dialog

tsx
import { Button } from "@/components/ui/button"import { ConfirmDialog } from "@/components/ui/sureui/confirm-dialog"<ConfirmDialog  title="Leave the Design team?"  description="An admin can add you back later."  confirmLabel="Leave team"  variant="destructive"  onConfirm={leaveTeam}>  <Button variant="outline">Leave team</Button></ConfirmDialog>

If onConfirm returns a promise, the dialog stays open until it settles.

variant styles only the confirm button. The trigger is your own element, so an outline trigger can open a destructive confirm step, as above.

Typed phrase

Pass phrase to require typing it before the confirm button unlocks, as on Type to Confirm.

Choices

Each item in choices adds a checkbox that doesn't block confirming. onConfirm receives their values by name, as they were when the person confirmed.

// Nothing has run yet

Alternative

alternative adds a softer action, such as archiving instead of deleting. It gets its own full-width row under Cancel and the confirm button, so the gentler choice is the easiest to reach. It doesn't need the phrase, and onCancel doesn't run. If onSelect returns a promise, the dialog stays open until it settles.

// Nothing has run yet

Initial focus

Focus starts on Cancel. initialFocus="confirm" starts on the confirm button, for actions that are easy to reverse, and "none" focuses the dialog itself. With phrase, focus starts in the first field, and "confirm" does the same, since the confirm button stays disabled until the phrase matches.

Keep "cancel" for anything that deletes data or can't be undone. The WAI-ARIA dialog pattern advises focusing the least destructive action when the step isn't easily reversed.

// Nothing has run yet

Hold

gesture works as on Confirm Button, here with "hold".

// Nothing has run yet

Arm delay

armDelay ignores the confirm button for that many milliseconds after the dialog opens, so a double click on the trigger can't pass through to it. It works as on Confirm Popover.

// Nothing has run yet

Wait

wait keeps the confirm button disabled for that many milliseconds after the dialog opens, with a countdown on it, so people read the dialog before confirming. It starts again each time the dialog opens, and works as on Confirm Button. With a phrase, typing already slows people down, so wait doesn't apply.

// Nothing has run yet

Errors

With onConfirmError, a failed onConfirm keeps the dialog open and the error goes to your handler. errorLabel replaces the confirm label until the next activation, which tries again.

// Nothing has run yet

Consequences

Pass a Consequences list to show what the action removes.

// Nothing has run yet

Awaiting the answer

useConfirm() returns a confirm() to await in a handler and a dialog to render once. Use it when confirming removes the button that opened the dialog, like a row that turns into a badge: dialog stays mounted, so it closes normally instead of disappearing.

// Nothing has run yet

Confirm a select change

A select or radio group can ask before its new value applies. Keep the field on the old value, await confirm(), and only set the new value when it resolves true. Cancelling changes nothing.

// Nothing has run yet

ConfirmDialog

For a way back after it closes, use Undo Toast.

PropTypeDefault
titleReactNoderequired
childrenReactElementrequired
onConfirm(choices: Record<string, boolean>) => void | Promise<unknown>required
onCancel() => void—
onConfirmError(error: unknown) => void—
descriptionReactNode—
consequencesReactNode—
confirmLabelReactNode"Confirm"
cancelLabelReactNode"Cancel"
errorLabelReactNode—
variantButton variant"default"
initialFocus"cancel" | "confirm" | "none""cancel", the first field with phrase
alternative{ label: ReactNode, onSelect: () => void | Promise<unknown> }—
gesture"click" | "click-again" | "hold" | "slide""click"
armDelaynumber0
waitnumber0
waitLabel(seconds: number) => ReactNode(s) => `Wait ${s}s`
timeoutnumber3000
durationnumber1200
holdFallback"click-again" | "none""click-again"
phrasestring | string[]—
caseSensitivebooleantrue
trimbooleanfalse
acknowledgementsstring[]—
choices{ name: string, label: ReactNode, defaultChecked?: boolean }[]—
announcements{ armed?, hold?, slide?, fallback?, wait?, match?, error? }—

useConfirm()

Takes no arguments.

NameTypeDescription
confirm(options: ConfirmOptions) => Promise<boolean>Opens the dialog. Resolves false on cancel, after alternative, when another confirm() replaces it, or on unmount.
dialogReactElementThe dialog. Render it once.
ConfirmOptionsobjectConfirmDialog props without children and onCancel. onConfirm is optional.
  • The dialog has the alertdialog role, named by title and described by description.
  • Focus moves into the dialog when it opens, to Cancel by default, and back to the trigger when it closes.
  • While onConfirm is pending, Cancel and Escape do nothing and the confirm button keeps focus. The same holds for the alternative's button while onSelect is pending.
  • With several phrases, each field has its own label and announces its own match.

WCAG

  • 2.2.1 Timing Adjustable: the dialog has no time limit. timeout only disarms click-again, and never runs or drops the action.
  • wait isn't a time limit under 2.2.1: it only delays when confirm unlocks, and never ends anything or drops the action.
  • 2.5.2 Pointer Cancellation: gesture="hold" meets it, because releasing early or moving off the button cancels.