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
verificationPhraseexactly. - Enter submits only when the gate is open; it's a no-op otherwise. Cancel, outside-click, and Escape all dismiss.
loadingdisables both buttons. The caller ownsopen— do not self-dismiss fromonConfirm. 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 Projectbutton,Project deletedtoast. NeverProject removed.
Content
titleis Title Case,Verb + Noun, a statement — never a question.Delete Project, notDelete this project?.descriptionis 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 thanThis project and all its deployments will be deleted..confirmLabelmatches 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 withverificationLabel="project name"so the prompt readsTo 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.irreversibleDescriptionnames the specific action and resource and ends withcannot be undone.—Deleting my-project cannot be undone.rather than the genericThis cannot be undone.. Omit the prop entirely for reversible actions; the prop's absence is the signal, not a falsy value.errorsurfaces 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
errortransition so the user can retry without losing context. After success, return focus to the trigger.
Was this helpful?