{"slug":"unsaved-changes-guard","meta":{"title":"Unsaved Changes Guard","slug":"unsaved-changes-guard","kind":"pattern","summary":"The screen knows whether it holds work that is not saved, says so continuously, and intercepts anything that would throw it away.","problem":"Someone fills in half a form, clicks a link, and the work is gone with no warning. Or the opposite: a screen warns about leaving when nothing was actually changed, so people learn to dismiss the warning and lose work anyway.","family":["edit","recover"],"data_shape":["record"],"principles":["orientation","consistency"],"interaction":["editing"],"density":"low","complexity":"medium","status":"stable","visibility":"public","use_when":["A screen holds work in progress that is not yet persisted.","Losing that work would cost more than a few seconds to redo.","Navigation away is easy — a nav bar, a back link, a drawer that closes on Escape."],"avoid_when":["The form autosaves. Then the answer is a save-state indicator, not a guard.","Nothing is lost by leaving, such as a filter or a search box.","The work is one field. A guard costs more attention than the retyping."],"alternatives":[{"slug":"autosave","when":"The better fix is to stop having unsaved changes at all."},{"slug":"confirmation-vs-undo","when":"The risk is an action being performed, not work in progress being lost."}],"ask_leo":"Add an unsaved-changes guard to this form.\n\n- Track whether the form is actually dirty by comparing current values to the\n  values it loaded with. Do not treat focus, or typing then retyping the same\n  value, as a change.\n- Show the state continuously and quietly — an \"Unsaved changes\" marker near\n  the save action — so leaving is never the first time someone learns about it.\n- Intercept in-app navigation: links, the back link, closing a drawer or\n  modal, and switching tabs within the record.\n- Also register the browser's own beforeunload warning for closing the tab or\n  hitting reload.\n- The interception is a dialog with three choices, not two: save and continue,\n  discard and continue, and cancel. A two-button dialog forces people to\n  choose between losing work and being stuck.\n- Say what will be lost — \"3 unsaved changes to this invoice\" — not \"you have\n  unsaved changes\".\n- Clear the state the moment a save succeeds, and never warn after a\n  successful save.\n","related":[{"title":"Confirmation vs Undo","url":"/patterns/confirmation-vs-undo","summary":"The general rule for when a screen should interrupt someone at all."}]},"body":"## Anatomy\n\n```\n┌──────────────────────────────────────────────┐\n│ Invoice INV-1041          ● Unsaved changes  │  ← continuous, quiet\n│ …fields…                    [ Save ]         │\n└──────────────────────────────────────────────┘\n\n  on navigate away:\n  ┌────────────────────────────────────────┐\n  │ Save your changes to INV-1041?         │\n  │ 3 fields have been edited.             │\n  │  [Cancel]  [Discard]  [Save and leave] │  ← three options, not two\n  └────────────────────────────────────────┘\n```\n\nThree obligations:\n\n1. **Real dirty tracking.** Compare against the loaded values. A guard that\n   fires because someone clicked into a field is the fastest way to teach people\n   to ignore it.\n2. **A continuous indicator.** The dialog should be a backstop, not the\n   announcement. If the only time you learn about unsaved work is when you try\n   to leave, the screen has been keeping a secret.\n3. **Three choices at the interception.** Save-and-continue is the one most\n   often missing, and it is the one people want most of the time.\n\n## Why it works\n\nIt converts an invisible risk into a visible state. Work-in-progress is real to\nthe person and invisible to the software unless someone chooses to model it —\nand once it *is* modelled, the indicator, the guard and the beforeunload\nhandler all fall out of the same flag.\n\nNaming the count matters more than it looks. \"You have unsaved changes\" is a\ncategory; \"3 unsaved changes to this invoice\" is a fact, and people make\ndifferent decisions about facts.\n\n## The better fix, where it is available\n\nA guard is a mitigation for a design where work can be lost. Where the domain\nallows it, [autosave](/patterns/autosave) removes the problem instead of\nmanaging it, and the screen then owes a save-state indicator rather than a\ndialog.\n\nThe honest test is how much a person would lose. Drafting a long note: autosave.\nA financial record where a half-entered state is meaningless or dangerous:\nexplicit save, with a guard.\n\n## Getting it wrong\n\n- **False positives.** Firing on focus, on a value typed and undone, or on a\n  field the system itself populated. Each one costs credibility, and credibility\n  is the only thing making the dialog work.\n- **Two buttons.** Cancel and Discard, with no way to save, is a dialog that\n  punishes the person for having done the right thing.\n- **Only the browser warning.** The generic beforeunload text cannot say what is\n  at stake, and it does not fire on in-app navigation at all.\n- **Warning after a successful save**, which happens whenever the flag is set on\n  input but never cleared on response.\n- **Blocking a genuine escape.** Someone must always be able to leave. The guard\n  asks; it does not trap.\n\n## Exemplars\n\n**Google Docs** is the strongest argument for the alternative: it removed the\nentire category by autosaving, and replaced the guard with a quiet\n\"All changes saved\" indicator — an idea now so normal that its absence feels\nlike a defect.\n\n**GitHub's pull request comment box** preserves drafts across navigation instead\nof guarding them, which is the third option: neither block nor lose, just keep.\n\n**Figma** shows the save-state indicator done as a continuous, low-attention\nelement rather than an interruption.\n\nThe extractable rule: **a guard is what you build when you have decided work can\nbe lost.** Before building it, check whether that decision was necessary — the\nbest version of this pattern is the one you did not need.\n"}