Skip to content

Confirmation Flows

Problem

Confirmation dialogs frequently drift into inconsistent behavior:

  • Different copy and button labels for the same action across screens
  • Actions continue even after the user cancels (callback soup)
  • Multiple dialog implementations per feature

Destructive actions are especially risky when combined with bulk selection.

Solution

Use useConfirmation as the single entry point for confirmation:

  • Call confirm(options) and await a boolean
  • Render exactly one dialog bound to the hook state
  • Centralize confirm copy, labels, and variants per action type

Implementation

Confirm before a destructive async operation

tsx
import React from 'react'
import { useAsyncAction, useConfirmation } from '@/shared/hooks'
import { ConfirmDialog } from '@/shared/ui/components/ConfirmDialog'

export function DeleteButton() {
  const { state, confirm, handleConfirm, handleCancel } = useConfirmation()

  const remove = useAsyncAction(async () => {
    // await repository.deleteThing()
    return true
  })

  const onClick = async () => {
    const ok = await confirm({
      title: 'Delete item?',
      description: 'This action cannot be undone.',
      confirmLabel: 'Delete',
      cancelLabel: 'Cancel',
      variant: 'destructive',
    })

    if (!ok) return

    await remove.run()
  }

  return (
    <>
      <button type="button" onClick={onClick} disabled={remove.isLoading}>
        {remove.isLoading ? 'Deleting…' : 'Delete'}
      </button>

      <ConfirmDialog
        open={state.isOpen}
        options={state.options}
        onConfirm={handleConfirm}
        onCancel={handleCancel}
      />
    </>
  )
}

Variations

  • Undo after confirm
    • Confirm → perform delete → show toast with Undo.
    • Undo restores items; keep selection cleared.
  • Escalated warnings
    • Use stronger copy and variant: 'destructive' for irreversible actions.
    • Include a list of affected items when feasible.
  • Confirm once, then execute multiple steps
    • Await confirm once, then run a sequence of operations (e.g., delete + revoke access).

Accessibility considerations

  • Ensure the dialog traps focus and supports Escape to close (Radix/shadcn AlertDialog covers this).
  • Keep titles and descriptions short and explicit.
  • Don’t rely on color alone to convey destructive intent.