The confirmation step in front of an irreversible action: the person has to type the resource's own name ("acme-prod") before the destructive button turns on. Reach for it wherever a misclick would be unrecoverable and a plain "Are you sure?" is too cheap: a settings page's danger zone, deleting a project, repository, workspace, organisation, team, cluster, database, bucket, index or environment, dropping a table or wiping seed data, removing a member or transferring ownership, revoking an API key, token or deploy key, rotating a secret, disconnecting an integration, force-pushing or deleting a branch, emptying a trash or archive, closing an account, and cancelling a paid subscription. Common asks it answers: "type to confirm", "type the project name to delete", "type DELETE to confirm", "confirm delete by typing name", "delete confirmation input react", "github style delete confirmation", "danger zone component react", "danger zone settings page", "destructive action modal react", "disable delete button until text matches", "confirm by typing resource name", "delete repository confirmation ui", "irreversible action confirmation react", "shadcn delete confirmation", "shadcn danger zone", "shadcn alert-dialog type to confirm", "react confirm delete dialog", "double confirmation delete react", "destructive button guard", "削除確認 入力", "プロジェクト名を入力して削除", "取り消せない操作の確認", "危険な操作 確認ダイアログ". Official shadcn/ui gives you alert-dialog, which is the shell and nothing else: the typed match, the disabled-until-it-matches wiring, the field that a phone must not autocapitalise, and the announcement when the button finally arms are left to be written again on every danger zone. This packages exactly those, and it sits inside the dialog or card you already have, so it assumes nothing about which official components are installed. The reason to install one rather than write the three lines is that the three-line version is wrong in four ways that only show up on somebody else's machine. A comparison without trimming fails for the pasted name, and a name copied out of a console or a URL bar routinely arrives with a trailing space or newline, so the person is looking at the right text and the button will not arm; both sides are trimmed here. An empty phrase matches an empty field under ===, so a dialog opened before its data has loaded arms the delete while the field is untouched; an empty phrase never matches here. On a phone the first letter is capitalised, autocorrected and spellchecked by default, which makes an exact match literally impossible to type, so the field opts out of autocomplete, autocorrect, autocapitalisation and spellcheck. And a live region that is inserted together with its text is not reliably announced, so a notice rendered only once the text matches swallows the very update it exists to make; the region here is always mounted and speaks the moment the button arms. The button is genuinely disabled rather than aria-disabled. Nothing is lost by that even though screen readers skip a disabled control, because the label says what to type, the description explains that the button is waiting for it, and the live region reports the change. It renders as a real <form>, so Enter submits and, inside a dialog, focus lands on the field on open with no extra wiring. Pass phrase and onConfirm and you are done; caseSensitive={false} lets "delete" pass for "DELETE", pending mirrors your mutation so the field locks and the button swaps to pendingLabel, and label, description and actionLabel are all overridable for another language or a softer tone. Every colour is a shadcn token (border-input, destructive, muted-foreground, ring), so light and dark follow on their own, and it depends on nothing but your cn util. Within pulld it is the heavy end of a pair: confirm-button is the two-step click for something cheap and reversible, and this is the guard for what cannot be taken back. unsaved-changes-guard stops you leaving work behind, where this one stops you destroying it.
pnpm dlx shadcn@latest add "https://pulld.pages.dev/r/type-to-confirm.json"