Skip to content

Destructive Actions

"Are you sure?" on every delete stops protecting anyone by the third time. People learn where OK is and click it without reading, and the one time it matters, the habit clicks through too.

If an action can be undone, do it right away and offer Undo. Save confirmation for what can't be undone.

Delete a few files each way.

Files5
  • Invoice 0412.pdf84 KB
  • Brand assets.zip12 MB
  • Q4 report.pdf2.1 MB
  • Team photo.jpg3.4 MB
  • Notes.md6 KB

0 clicks to delete 0 files

With a dialog on every row, three files take six clicks, and every dialog asks the same question. With Undo they take three. The file goes as soon as you press, and a toast keeps Undo around for 5 seconds. Delete another while it's up and Undo brings both back where they were.

Aza Raskin made this case in 2007: never use a warning when you mean undo. A warning asks people to predict a mistake. Undo lets them notice it afterwards, which is when people actually notice.

Undo needs a soft delete behind it. Hide the row, keep it with a deletedAt, and purge it later. The toast goes away, so give deleted things a home that stays, like a Trash that keeps them for 30 days.

When you have to ask

Some things can't come back, like a project with all its deployments. Here a confirmation earns its place, as long as it says something. Open the dialog and press Enter.

Critly website

Project settings

Domaincraft.dev
Deployments214

Delete project

Removes the project and everything in it.

Open the dialog, then press Enter

"Are you sure?" could be about anything, and with focus on OK, Enter deletes the project. The specific dialog names the project, says what goes with it, and the button repeats the verb: "Delete project", not "OK". Focus starts on Cancel, so a stray Enter does nothing. The rest follows the overlays rules: focus goes in, Escape cancels, and focus comes back to the button.

For the catastrophic, add friction on purpose. GitHub asks you to type the repository's full name before it deletes one. It works because it's rare. Ask for it on every delete and typing becomes one more reflex.

Red where it counts

Red is a warning, and warnings wear out. Keep it for the button that actually destroys something. The trash icons in the file list stay grey and turn red on hover, one of their interaction states rather than a color they wear all the time.

Usage

app.tsx
<dialog id="delete" role="alertdialog" aria-labelledby="delete-title"
  class="max-w-sm rounded-xl p-4 backdrop:bg-black/20">
  <h2 id="delete-title" class="text-sm font-medium">Delete “Critly website”?</h2>
  <p class="mt-1.5 text-sm text-neutral-500">
    This permanently deletes 214 deployments, 3 environments and the craft.dev domain.
  </p>
  <form method="dialog" class="mt-4 flex justify-end gap-2">
    <button value="cancel" autofocus class="rounded-full px-3 py-1.5 text-sm">
      Cancel
    </button>
    <button value="delete" class="rounded-full bg-red-500/10 px-3 py-1.5 text-sm text-red-600">
      Delete project
    </button>
  </form>
</dialog>
import { toast } from "sonner";

function remove(file: File) {
  // Soft delete: hidden now, purged later
  softDelete(file.id);

  toast(`Deleted “${file.name}”`, {
    duration: 5000,
    action: { label: "Undo", onClick: () => restore(file.id) },
  });
}

Undo has to reverse everything the delete set off. If removing a file also emails its collaborators, bringing the file back doesn't unsend the emails. Hold side effects like these until the undo window has closed.

Resources

Search concepts

Search for a command to run...