Forgiveness through reversibility
Reversibility is the highest-leverage form of forgiveness short of structural prevention. It makes destructive actions safe by allowing recovery — users keep speed (no friction up front) and safety (the action is undoable). Whenever an action can be made reversible, that's almost always preferable to confirming it.
The reversibility ladder
Different actions need different reversibility approaches:
Synchronous undo (Cmd-Z)
The classic. The user takes an action; pressing Cmd-Z (or Ctrl-Z) reverses it. Multi-level undo lets the user back out through a sequence of actions.
When it applies: editing operations on a single object — text editing, image manipulation, design tool actions, code edits.
Implementation pattern:
- Maintain an undo stack of inverse operations.
- On each action, push the inverse onto the stack.
- On Cmd-Z, pop and apply the inverse.
- On any new action after undo, optionally clear the redo stack (or maintain redo separately).
const undoStack = [];
const redoStack = [];
function executeAction(action) {
action.do();
undoStack.push(action);
redoStack.length = 0;
}
function undo() {
const action = undoStack.pop();
if (!action) return;
action.undo();
redoStack.push(action);
}
function redo() {
const action = redoStack.pop();
if (!action) return;
action.do();
undoStack.push(action);
}
Toast-with-undo (the modern default for one-off destructive actions)
The user takes an action that destroys or moves data; a toast appears with an "Undo" button for several seconds.
When it applies: archive, delete, move-to-folder, mark-read, and similar one-shot list actions.
Implementation pattern:
- Optimistically update the UI (item disappears).
- Send the destructive request to the server (or defer it for the undo window).
- Show a toast with an Undo affordance for 5–10 seconds.
- On Undo: reverse the optimistic update, cancel/reverse the server action.
- On timeout: commit the action permanently.
async function archive(item) {
const previous = items.slice();
items = items.filter(i => i.id !== item.id);
render();
let undone = false;
showToast(`"${item.name}" archived`, {
action: { label: 'Undo', onClick: () => { undone = true; items = previous; render(); } },
duration: 7000,
onDismiss: () => { if (!undone) api.commitArchive(item.id); }
});
}
The user gets the speed of "click and it happens" plus the safety of "you have 7 seconds to undo."
Soft delete with explicit recovery
A delete that doesn't actually remove the data — instead moves it to a recoverable state for some window (30 days, indefinitely, until storage cleanup).
When it applies: account-level data the user might want back later; emails, documents, files. Common in B2B SaaS.
Implementation pattern:
- Mark the item as deleted with a timestamp.
- Hide it from default views.
- Provide a "Trash" or "Archive" view where deleted items can be restored.
- After the recovery window, hard-delete via background job.
// Soft delete
async function softDelete(item) {
await api.update(item.id, { deletedAt: new Date(), deletedBy: currentUser.id });
}
// In default queries
const visibleItems = items.filter(i => !i.deletedAt);
// Recovery view
const trashedItems = items.filter(i => i.deletedAt && daysAgo(i.deletedAt) < 30);
async function restore(item) {
await api.update(item.id, { deletedAt: null, deletedBy: null });
}
Version history
The strongest reversibility: every state of the object is preserved; the user can return to any prior state.
When it applies: documents, code, designs, configurations — anything users edit over time and might want to revert.
Implementation pattern:
- Snapshot the object on each meaningful change (or at intervals, or on explicit save).
- Provide a "Version history" UI listing past states.
- Allow viewing, restoring, or branching from any prior version.
Google Docs, Notion, Figma, GitHub all implement this. The user never fears editing because they can always restore.
The "Undo send" pattern
Gmail's pattern: an action that appears committed actually defers for a few seconds, allowing cancellation in that window.
When it applies: actions that the user often regrets immediately after triggering — sending an email, posting publicly, sending a message.
Implementation pattern:
- On "Send," show "Sending..." with an Undo affordance for N seconds.
- Don't actually transmit until N seconds elapse with no Undo.
- On Undo, return to draft state.
The user feels they sent it; the system just defers commitment briefly. The defer window is short (5–30 seconds) — long enough to catch reflexive "wait!" moments, short enough not to delay genuine sends.
Choosing the right reversibility approach
| Action type | Recommended reversibility |
|---|---|
| Text/edit/format change | Synchronous undo (Cmd-Z) |
| Archive / soft delete one item | Toast-with-undo |
| Hard delete / account changes | Soft delete + recovery view |
| Document edits | Version history + Cmd-Z |
| Send / publish / post | Undo-send window |
| Bulk destructive | Soft delete + bulk-restore in trash |
When reversibility isn't possible
Some actions genuinely can't be reversed at the data layer:
- External communications (sent emails, sent SMS, public tweets) — the recipient already saw it.
- Financial transfers — money has moved.
- Compliance / legal commits — once signed, signed.
- Hardware operations — once a physical command goes to a robot or printer, it's done.
For these, lean on the next-strongest forgiveness levels: structural prevention (good affordances), confirmation (deliberate intent), and warnings.
The "Undo send" pattern bridges this for some communications: the external action is still irreversible, but the system delays it long enough for the user to catch second thoughts.
Recovery window length
For toast-with-undo: 5–10 seconds is the typical range.
- < 5 seconds: too short for users who realized after a moment of reflection.
- 5–7 seconds: covers most reflexive corrections.
- 10+ seconds: lingers; some users dismiss the toast before reading it.
For soft delete: 7–30 days is typical.
- 7 days: balances storage cost with user recovery needs.
- 30 days: standard for most SaaS; forgiving enough for vacations and weekends.
- Indefinite: storage cost grows but maximum forgiveness.
Discoverability of recovery
A reversibility mechanism that users don't know exists doesn't help. Patterns:
- Toast affordance is its own discovery — users see the Undo button and learn the pattern.
- "Trash" or "Archive" navigation entry signals that deleted items can be recovered.
- Onboarding mention for version history ("All changes saved. View history at any time.").
If users are filing tickets to recover data they could have restored themselves, the recovery affordance isn't visible enough.
Anti-patterns
- Toast that vanishes too fast. A 2-second toast with an Undo button most users never see.
- Soft delete with no recovery UI. Items go to a "trash" the user can't access.
- Version history that's hidden. Buried under three menu levels; users don't know it exists.
- Cmd-Z that doesn't undo destructive actions. Some apps undo formatting but not deletion. Inconsistent.
- Undo that fails silently. Click Undo; nothing happens; no error; user is confused. If undo fails, say so.
Heuristics
- The "did they recover?" check. Look at support tickets for data loss. Each one is a reversibility failure.
- The toast-button audit. For every destructive action, is there a toast with Undo? If not, why not?
- The trash audit. Is there a Trash / Archive / Recycle Bin in your app? If items can be deleted but there's no recovery view, you're missing soft delete.
Related sub-skills
forgiveness(parent).forgiveness-confirmation-and-prevention— the alternative when reversibility isn't possible.feedback-loop— toast-with-undo is feedback that doubles as reversibility.factor-of-safety(process) — engineering analog of "design with margin so errors are recoverable."