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
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.
| Prop | Type | Default |
|---|---|---|
| title | ReactNode | required |
| children | ReactElement | required |
| onConfirm | (choices: Record<string, boolean>) => void | Promise<unknown> | required |
| onCancel | () => void | — |
| onConfirmError | (error: unknown) => void | — |
| description | ReactNode | — |
| consequences | ReactNode | — |
| confirmLabel | ReactNode | "Confirm" |
| cancelLabel | ReactNode | "Cancel" |
| errorLabel | ReactNode | — |
| variant | Button 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" |
| armDelay | number | 0 |
| wait | number | 0 |
| waitLabel | (seconds: number) => ReactNode | (s) => `Wait ${s}s` |
| timeout | number | 3000 |
| duration | number | 1200 |
| holdFallback | "click-again" | "none" | "click-again" |
| phrase | string | string[] | — |
| caseSensitive | boolean | true |
| trim | boolean | false |
| acknowledgements | string[] | — |
| choices | { name: string, label: ReactNode, defaultChecked?: boolean }[] | — |
| announcements | { armed?, hold?, slide?, fallback?, wait?, match?, error? } | — |
useConfirm()
Takes no arguments.
| Name | Type | Description |
|---|---|---|
| confirm | (options: ConfirmOptions) => Promise<boolean> | Opens the dialog. Resolves false on cancel, after alternative, when another confirm() replaces it, or on unmount. |
| dialog | ReactElement | The dialog. Render it once. |
| ConfirmOptions | object | ConfirmDialog props without children and onCancel. onConfirm is optional. |
- The dialog has the
alertdialogrole, named bytitleand described bydescription. - Focus moves into the dialog when it opens, to Cancel by default, and back to the trigger when it closes.
- While
onConfirmis pending, Cancel and Escape do nothing and the confirm button keeps focus. The same holds for the alternative's button whileonSelectis 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.
timeoutonly disarmsclick-again, and never runs or drops the action. waitisn'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.