Unsaved Changes Guard
Guards against data loss when leaving a page with unsaved changes — combines ConfirmDialog (in-app confirm) and window.beforeunload (browser confirm). SvelteKit pattern with a beforeNavigate hook for in-app route changes.
Live Preview
Features
- dirty flag as a $state signal — every form input sets it to true on input
- beforeNavigate (SvelteKit) intercepts in-app route changes → ConfirmDialog
- window.beforeunload for browser close, refresh, external links → native browser confirm
- Cleanup-safe — listeners are deregistered in the $effect cleanup
- Reusable as a use hook (no component mount required)
Code
lib/use-unsaved-guard.svelte.ts
Usage in a form page
Best Practices
Three actions instead of two
Save and leave, Discard, Cancel — the common mistake is leaving out "Discard". That forces the user to either save (even broken data) or not navigate at all. Three clear paths solve it.
dirty flag from a real diff
dirty should come from a comparison against the
original state, not from a manual flag. Otherwise you risk false positives (the user
types something and deletes it again → no diff, but the flag is still true). $derived(name !== originalName) is robust.
Don't overload browser beforeunload
Modern browsers no longer show app-specific text for beforeunload — just a generic "Do you really want
to leave this page?". So beforeunload is only a fallback
for browser close/refresh. The more important protection is the app's own ConfirmDialog on
internal route changes.
Auto-save as an alternative
If the schema allows auto-save (settings, profile, notes), it spares you the whole guard. The guard is needed when saving is explicit (wizard with submit, form with validation). Ask yourself: would auto-save break the user's workflow? If not → no guard needed.
Why a recipe instead of a component?
The guard is app state, not a UI pattern: dirty tracking, save action, discard action,
and the beforeNavigate integration are all app-specific. A UIB component <UnsavedChangesGuard> would only offer convenience
for 5 lines of setup — at noticeably more coupling. Recipe + use hook is the clean separation.