Destructive Action Modal

Confirm destructive actions with a required type-to-confirm gate and an optional irreversibility band.

Default

A type-to-confirm gate disables submit until the user types the verification phrase exactly. The red striped band at the bottom names what cannot be undone.

Reversible

Omit irreversibleDescription for actions that can be re-enabled, undone, or rolled back. The type-to-confirm gate stays — the friction is still warranted — but the red band is skipped.

Loading

loading disables both buttons and shows a spinner on the primary action. Use while the destructive API call is in flight. Caller controls open; don't dismiss the modal from inside.

With error

Pass error (a string or an Error) to surface an inline error under the input. The modal stays open so the user can retry.

Best Practices

When to use

  • Reach for this over Modal when the action is destructive and warrants friction: delete, rotate, revoke, disconnect, downgrade, disable a security setting. The typed gate forces deliberate intent.
  • Use it for reversible destructive actions too, when the consequence is serious enough to warrant a pause — disabling deployment protection, revoking a shared token. Keep the typed gate; drop irreversibleDescription.
  • Don't use for routine confirmations (save draft, discard changes, close without saving). The typed gate reads as melodrama when the stakes are low — use a plain Modal instead.

Behavior

  • The verification input gets focus on open so the user can start typing immediately. Submit stays disabled until the value matches verificationPhrase exactly.
  • Enter submits only when the gate is open; it's a no-op otherwise. Cancel, outside-click, and Escape all dismiss.
  • loading disables both buttons. The caller owns open — do not self-dismiss from onConfirm. Close the modal after the API settles (success or error), or keep it open on error so the user can retry.
  • Pair the post-confirm success toast verb 1:1 with the primary button: Delete Project button, Project deleted toast. Never Project removed.

Content

  • title is Title Case, Verb + Noun, a statement — never a question. Delete Project, not Delete this project?.
  • description is sentence case, names the consequence, and interpolates the specific resource when relevant. <b>my-project</b> and all its deployments will be permanently deleted. reads stronger than This project and all its deployments will be deleted..
  • confirmLabel matches the title 1:1. Never generic (Confirm, OK, Continue), never a bare verb (Delete).
  • verificationPhrase: for entity deletes, type the resource name itself (my-project) and pair with verificationLabel="project name" so the prompt reads To confirm, type the project name "my-project". That's the canonical signal that the user knows which thing they're acting on. Fall back to a lowercase verb phrase (disable vercel authentication) only when there's no entity to name.
  • irreversibleDescription names the specific action and resource and ends with cannot be undone.Deleting my-project cannot be undone. rather than the generic This cannot be undone.. Omit the prop entirely for reversible actions; the prop's absence is the signal, not a falsy value.
  • error surfaces the API failure verbatim in Vercel voice: Couldn't save settings. Try again.. Never dump a raw error object.

Accessibility

  • The verification input is associated with its prompt via aria-labelledby + htmlFor. Screen readers announce the full prompt (To confirm, type "delete my-project") on focus.
  • The Warning icon in the irreversibility band is aria-hidden; the sentence carries the meaning so nothing is announced twice.
  • Focus stays inside the modal across an error transition so the user can retry without losing context. After success, return focus to the trigger.