Source Code

<div class="container py-5 d-flex justify-content-center">
  <div class="card bsuca-card">
    <div class="card-body p-4">
      <div class="d-flex justify-content-between align-items-center mb-3">
        <h5 class="fw-bold mb-0">Profile settings</h5>
        <button type="button" class="btn btn-sm btn-outline-secondary" id="bsucaLeave">&larr; Back to dashboard</button>
      </div>

      <div class="mb-3">
        <label class="form-label small fw-semibold">Display name</label>
        <input type="text" class="form-control" id="bsucaName" value="Priya Nair">
      </div>
      <div class="mb-3">
        <label class="form-label small fw-semibold">Bio</label>
        <textarea class="form-control" id="bsucaBio" rows="3">Frontend engineer. Building small, fast things.</textarea>
      </div>

      <div class="d-flex align-items-center gap-2">
        <button type="button" class="btn btn-dark fw-bold" id="bsucaSave">Save changes</button>
        <span class="small" id="bsucaStatus">No changes yet</span>
      </div>
    </div>
  </div>
</div>

<div class="modal fade" id="bsucaModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title fw-bold">Unsaved changes</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <p class="mb-0">You've edited this form but haven't saved. Leaving now will discard those changes.</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Keep editing</button>
        <button type="button" class="btn btn-outline-danger" id="bsucaDiscard">Discard &amp; leave</button>
        <button type="button" class="btn btn-dark fw-bold" id="bsucaSaveLeave">Save &amp; leave</button>
      </div>
    </div>
  </div>
</div>

Bootstrap Unsaved Changes Alert — Free HTML CSS JS Snippet

Bootstrap Unsaved Changes Alert · Forms · Plain HTML, CSS & JS · Live preview

What's included

Features

A single isDirty flag driven by real input events, not a guess
A Bootstrap modal with three real outcomes: keep editing, discard, or save-then-leave
Discard restores the exact last-saved values instead of just closing the modal
A genuine window.beforeunload guard for real tab-close/navigation protection
The nag never fires on a clean form — beforeunload is skipped entirely when nothing changed
One save() function is the single source of truth for what "saved" means

About this UI Snippet

Bootstrap Unsaved Changes Alert — HTML, CSS & JavaScript

Screenshot of the Bootstrap Unsaved Changes Alert snippet rendered live

Two separate things have to work for this pattern to be real rather than decorative: knowing the form is actually dirty, and knowing when the user is actually trying to leave. This snippet tracks the first with a single isDirty flag flipped by an input listener on every field, and it tracks the second two ways at once — a same-page click on "Back to dashboard" (interceptable, so it can show a real Bootstrap modal with three genuine choices) and a native beforeunload listener (for a real tab close or address-bar navigation, which no in-page modal can ever intercept).

The three modal buttons aren't decoration either — "Discard & leave" restores nameInput/bioInput to the last saved values before closing, "Save & leave" runs the exact same save() function the visible Save button uses, and "Keep editing" is just Bootstrap's own data-bs-dismiss. Because save() is the single function that updates the saved snapshot and clears isDirty, there's no separate "did the modal's save actually work" bookkeeping to keep in sync with the main form.

The beforeunload listener is guarded behind if (!isDirty) return specifically so it never fires for a clean form — a form that nags on every exit regardless of whether anything changed trains users to click through the browser's own dialog without reading it, which defeats the point of having one at all.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Hand this snippet to an AI coding assistant like Claude and ask it to wire the same isDirty flag into a real client-side router's navigation guard (React Router's blocker, Vue Router's beforeRouteLeave, or Angular's CanDeactivate) instead of a single button click, so the warning covers every route change in a real single-page app.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a Bootstrap 5.3 form that warns before losing unsaved changes, using the real Bootstrap CDN framework (bootstrap.min.css and bootstrap.bundle.min.js), not custom CSS made to resemble it.

Requirements:
- A card containing at least two form fields and a visible "Save changes" button.
- Track a single isDirty boolean, set true by an input listener on every field and cleared only when Save is explicitly clicked.
- A small status line reflecting the current state: "No changes yet", "Unsaved changes", or "Saved".
- A "Back to dashboard" button that, when isDirty is true, opens a Bootstrap modal with three real actions: keep editing (dismiss), discard changes and leave (restore the fields to their last saved values), and save then leave (run the same save logic as the Save button). When isDirty is false, clicking it should do nothing but immediately proceed.
- Also attach a window.beforeunload listener that calls preventDefault only when isDirty is true, for real browser-level protection against a tab close or address-bar navigation.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Load the snippetThe status line reads "No changes yet" and the form is untouched.
  2. 2
    Edit the name or bio fieldThe status flips to "Unsaved changes" the instant you type.
  3. 3
    Click "Back to dashboard" while dirtyA modal appears offering to keep editing, discard, or save before leaving.
  4. 4
    Click "Discard & leave"Both fields revert to their last saved values and the modal closes.
  5. 5
    Edit again, then click "Save changes"The status turns green and reads "Saved" — the dirty flag clears.
  6. 6
    Click "Back to dashboard" nowNothing to lose, so it leaves immediately with no modal at all.

Real-world uses

Common Use Cases

Settings and profile pages
Anywhere a user edits a form across several fields before a deliberate Save, this prevents an accidental tab-close from losing the work.
Admin and CMS content editors
Pairs naturally with something like bootstrap-inline-form-editing wherever edits happen in place.
CART
Multi-field checkout or order forms
Warn a shopper before an accidental back-button tap wipes out a partially filled shipping form.
Learning the difference between in-app and browser-level navigation
A compact, real example of why a page needs both an interceptable click handler and a beforeunload listener — one can never substitute for the other.

Got questions?

Frequently Asked Questions

No — sandboxed iframes and most browsers suppress custom beforeunload dialogs outside of a real top-level page, and some browsers show only a generic built-in message regardless of e.returnValue. The listener itself is real and correct; only the in-page modal demo is fully visible here.

Both approaches work — this snippet uses a flag for simplicity, but comparing current values against the saved snapshot on demand is equally valid and avoids ever getting the flag out of sync, at the cost of a slightly more expensive check.

Yes — add every field that should count as "dirty" to the array passed to forEach, and add its restore logic to Discard the same way nameInput and bioInput are handled.

Yes. In React, track isDirty and the saved snapshot in useState and open/close the modal via a ref to Bootstrap's Modal API or a controlled modal component; the beforeunload listener belongs in a useEffect that re-attaches whenever isDirty changes.

No — they solve different problems and pair well together. See bootstrap-form-autosave-status for a pattern that removes the need for this warning almost entirely by saving continuously instead of on a single explicit action.