Skip to content

ConfirmDialog

<ConfirmDialog> is the production-grade confirmation primitive — a single component that handles the trigger, the modal, async submission, the pending state, and rollback on error. Use this in 90% of cases instead of authoring AlertDialog by hand.

import from "@na/ui/components/ConfirmDialog" ▶ Open in Storybook packages/ui/src/components/ConfirmDialog.tsx

When to use

  • Destructive verbs: delete, archive, reset.
  • Publish / unpublish or any other “applies to other users” operation.
  • Anything async that requires explicit confirmation + a pending state.

Don’t use ConfirmDialog for: routine save (no confirmation; just save), notifications (Sonner), inline confirms (ConfirmPopover).

Destructive

The pending state shows pendingLabel while onConfirm resolves. Click again is suppressed during pending.

<ConfirmDialog
trigger={<Button variant="destructive">Delete agent</Button>}
title="Delete this agent?"
description="This action cannot be undone. Run history will be permanently removed."
confirmLabel="Delete"
variant="destructive"
onConfirm={async () => {
await api.delete(agent.id);
}}
/>

Default (non-destructive)

<ConfirmDialog
trigger={<Button>Publish v3</Button>}
title="Publish a new version?"
description="A new immutable version will be created."
confirmLabel="Publish"
onConfirm={async () => api.publish(agent.id)}
/>

API

Prop Type Default Description
trigger * ReactNode Element that opens the dialog when clicked. Pass a <Button>.
title * string Verb-led question: "Delete this agent?"
description * string Single sentence stating the consequence.
confirmLabel * string Verb-only label on the confirm button. Never default to "Confirm".
cancelLabel string "Cancel" Override for i18n.
pendingLabel string "Processing…" Shown on the confirm button while onConfirm pending.
onConfirm * () => unknown | Promise<unknown> Resolved → close. Rejected → stay open and surface the error.
variant "default" | "destructive" "default" Tints the confirm button red when destructive.

Design guidelines

✓ Do

  • Use the verb that names the action: "Delete", "Publish", "Archive". Never "OK" or "Confirm".
  • Make the description state the consequence in one sentence — no ambiguity.
  • Let the component handle pending — never manage isLoading separately.

✗ Don't

  • Use ConfirmDialog for save / cancel forms. That's the wrong gravity.
  • Skip the description because the title "looks complete." Without consequence framing, users click yes.
  • Catch the error inside onConfirm and resolve. Throw it; ConfirmDialog rollbacks the pending state.

Accessibility

  • Inherits AlertDialog semantics — focus trap, Esc = cancel, focus returns to the trigger.
  • Cancel is keyboard-default focus.

▶ Open ConfirmDialog stories in Storybook