Confirm Popover
A popover anchored to its trigger with one line about the action and a confirm button.
// Nothing has run yet
npx shadcn@latest add @sureui/confirm-popover
import { Button } from "@/components/ui/button"import { ConfirmPopover } from "@/components/ui/sureui/confirm-popover"<ConfirmPopover description="Open pull requests from this branch will close." confirmLabel="Delete branch" variant="destructive" onConfirm={deleteBranch}> <Button variant="outline">Delete branch</Button></ConfirmPopover>Cancel, Escape, a click outside or tabbing out close it and call onCancel.
variant styles only the confirm button. The trigger is your own element, so an outline trigger can open a destructive confirm step, as above.
Hold
gesture works as on Confirm Button, here with "hold".
// Nothing has run yet
Title
Add a title to show a heading above the description.
// Nothing has run yet
Placement
side picks where the popover opens, and align lines it up with the trigger.
// Nothing has run yet
Arm delay
armDelay ignores the confirm button for that many milliseconds after the popover opens, so a double click on the trigger can't pass through to it.
// Nothing has run yet
Wait
wait keeps the confirm button disabled for that many milliseconds after the popover opens, with a countdown on it. It works as on Confirm Button.
// Nothing has run yet
Errors
With onConfirmError, a failed onConfirm keeps the popover open and the error goes to your handler. errorLabel replaces the confirm label until the next activation, which tries again.
// Nothing has run yet
Initial focus
Focus starts on the confirm button, so a keyboard user can confirm a quick action with one more key. initialFocus="cancel" starts on Cancel instead, and "none" focuses the popover itself. For an action that deletes data or can't be undone, the WAI-ARIA dialog pattern advises focusing the least destructive action.
// Nothing has run yet
ConfirmPopover
It has no undo, since it closes when the action commits. Use Undo Toast for a way back.
| Prop | Type | Default |
|---|---|---|
| description | ReactNode | required |
| children | ReactElement | required |
| onConfirm | () => void | Promise<unknown> | required |
| onCancel | () => void | — |
| onConfirmError | (error: unknown) => void | — |
| title | ReactNode | — |
| confirmLabel | ReactNode | "Confirm" |
| errorLabel | ReactNode | — |
| cancelLabel | ReactNode | "Cancel" |
| initialFocus | "confirm" | "cancel" | "none" | "confirm" |
| variant | Button variant | "default" |
| side | "top" | "bottom" | "left" | "right" | "inline-start" | "inline-end" | "bottom" |
| align | "start" | "center" | "end" | "center" |
| open | boolean | — |
| onOpenChange | (open: boolean) => void | — |
| 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" |
| announcements | { armed?, hold?, slide?, fallback?, wait?, error? } | — |
- The popover is named by
title, or bydescriptionwhen there's no title. - Focus moves to the confirm button when it opens, or where
initialFocussays, and back to the trigger when it closes. - While
onConfirmis pending, the popover stays open and the confirm button keeps focus.
WCAG
- 2.2.1 Timing Adjustable: the popover 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.