Gesso
Overlays

Confirm

An alert dialog with a trigger, a verb, and a pending state. The trigger is optional when open is controlled.

Confirm asks before an action that is hard to undo. It is a composition of Alert dialog and Button rather than a new overlay, so you pass a trigger, a verb, and optionally pending. The dialog has no close button and ignores clicks outside it, and Action and Cancel are kit Buttons. What sets it apart is the pending verb: the confirm button shows its spinner, Cancel is disabled, Escape is locked, and the dialog is marked busy until your handler settles.

Usage

import { Button } from '@/components/ui/button';
import { Confirm } from '@/components/ui/confirm';

<Confirm
  trigger={<Button variant="destructive">Delete</Button>}
  title="Delete this look?"
  description="This cannot be undone."
  verb="Delete"
  variant="destructive"
  pending={isDeleting}
  onConfirm={() => deleteLook()}
/>

Pending and disabled

Return a promise from onConfirm and the verb pends until it settles. The dialog stays open if the promise rejects, so you can show the error and let the person try again. Pass pending to drive the same state from outside, and it combines with a promise that is still running. disabled disables the trigger.

Without a trigger

Leave out trigger when something else opens the dialog, such as a Menu item or a keyboard shortcut. open and onOpenChange are then required. On close, focus goes back to whatever held it when the dialog opened, if that is still on the page. A menu item is gone by then, so pass the menu's trigger as returnFocusRef.

Spring newsletter
const [open, setOpen] = React.useState(false);
const menuTriggerRef = React.useRef<HTMLButtonElement>(null);

<Menu>
  <MenuTrigger asChild>
    <IconButton ref={menuTriggerRef} icon={DotsThreeVertical} label="Actions" />
  </MenuTrigger>
  <MenuContent>
    <MenuItem destructive onSelect={() => setOpen(true)}>Cancel send</MenuItem>
  </MenuContent>
</Menu>
<Confirm
  open={open}
  onOpenChange={setOpen}
  returnFocusRef={menuTriggerRef}
  title="Cancel this send?"
  verb="Cancel send"
  variant="destructive"
  onConfirm={cancelSend}
/>

Props

PropTypeDefaultWhat it does
triggerReactElementThe single element that opens the dialog. Omit it when open is controlled.
titleReactNodeThe dialog title, which is its accessible name.
descriptionReactNodeSupporting copy under the title.
verbReactNodeThe label on the confirm button.
cancelLabelReactNode"Cancel"The label on the cancel button.
variant"default" | "destructive""default"The weight of the confirm button.
pendingbooleanfalseHolds the pending state from outside, combined with a running onConfirm.
disabledbooleanfalseDisables the trigger.
onConfirm() => void | Promise<void>Runs on confirm. Return a promise to pend the verb.
openbooleanControls the open state. Required without trigger.
defaultOpenbooleanfalseThe initial open state when uncontrolled. Needs trigger.
onOpenChange(open: boolean) => voidCalled when the open state changes. Required without trigger.
returnFocusRefRefObject<HTMLElement | null>Without trigger, where focus lands on close.

Accessibility

  • The dialog has role="alertdialog" and aria-modal from Radix Alert dialog, the title names it, and the description describes it.
  • Focus moves into the dialog on open, and it returns to the trigger on close. Without a trigger it returns to returnFocusRef, or to whatever held focus when the dialog opened.
  • Escape closes the dialog unless the verb is pending, Tab stays inside it, and a click outside does not dismiss it.
  • While pending the dialog is aria-busy and Cancel is disabled. The spinner is the visible sign, so keep the verb's label meaningful, because nothing else is announced when it starts.
  • Action and Cancel are Buttons, so they have the same :focus-visible ring, the transparent outline in forced colors, and at least 44px of height on a touch screen.
  • Under reduced motion the dialog opens without its pop, and the spinner keeps turning.

When to use something else

  • Alert dialog when the action needs no pending state, or when you want to compose the buttons yourself.
  • Dialog when the person fills something in rather than answering yes or no.
  • Sheet or Drawer for a longer task that should not block the page.

Install

shadcn add @gesso/confirm

On this page