Skip to main content
Urbicon UI
← Back to Recipes

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.

ConfirmDialog

Live Preview

No changes

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

Loading...
Loading syntax highlighting...

Usage in a form page

Loading...
Loading syntax highlighting...

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.