Source Code

<div class="wnc-page"><button type="button" class="wnc-open" id="wncOpen">Open app <span class="wnc-dot" id="wncDot"></span></button></div>

<div class="wnc-backdrop" id="wncBackdrop"></div>
<div class="wnc-modal" id="wncModal" role="dialog" aria-modal="true" aria-labelledby="wncTitle">
  <button type="button" class="wnc-close" id="wncClose" aria-label="Close">✕</button>

  <div class="wnc-head">
    <span class="wnc-eyebrow">Version 4.2</span>
    <h3 id="wncTitle">What's new</h3>
  </div>

  <ul class="wnc-list">
    <li class="wnc-entry">
      <span class="wnc-icon wnc-icon-new">NEW</span>
      <div>
        <h4>Real-time collaboration</h4>
        <p>See teammates' cursors and edits appear live, no more refresh-to-sync.</p>
      </div>
    </li>
    <li class="wnc-entry">
      <span class="wnc-icon wnc-icon-improved">IMPROVED</span>
      <div>
        <h4>40% faster page loads</h4>
        <p>We rebuilt the data layer — dashboards with large datasets now load almost instantly.</p>
      </div>
    </li>
    <li class="wnc-entry">
      <span class="wnc-icon wnc-icon-fixed">FIXED</span>
      <div>
        <h4>Export bug on Safari</h4>
        <p>CSV exports containing emoji no longer produce corrupted files on Safari.</p>
      </div>
    </li>
    <li class="wnc-entry">
      <span class="wnc-icon wnc-icon-new">NEW</span>
      <div>
        <h4>Keyboard shortcuts panel</h4>
        <p>Press <kbd>?</kbd> anywhere in the app to see every available shortcut.</p>
      </div>
    </li>
  </ul>

  <div class="wnc-footer">
    <label class="wnc-dont-show">
      <input type="checkbox" id="wncDontShow">
      <span>Don't show this again</span>
    </label>
    <button type="button" class="wnc-got-it" id="wncGotIt">Got it</button>
  </div>
</div>

What's New Changelog Modal — Free HTML CSS JS Snippet

What's New Changelog Modal · Modals · Plain HTML, CSS & JS · Live preview

What's included

Features

Auto-opens once per version, tracked via a localStorage version string comparison
Independent "don't show again" mute flag, separate from the per-version seen tracking
Notification dot appears alongside a delayed auto-open, avoiding a jarring instant popup
Bumping a single CURRENT_VERSION constant is the entire release mechanism
localStorage reads and writes wrapped in try/catch for private-browsing and quota safety
Changelog entries categorized as NEW, IMPROVED, or FIXED with distinct colored labels
Manual open trigger works independently of the auto-open-on-new-version logic
Escape key, backdrop click, and a close button all dismiss the modal
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons

About this UI Snippet

What's New Changelog Modal — Auto-Opens Once Per Version via localStorage

Screenshot of the What's New Changelog Modal snippet rendered live

Release notes only matter if someone sees them once, at the right moment — not every time they open the app. This modal tracks which version a visitor last saw in localStorage and only auto-opens itself when the current version is genuinely new to them, with a separate "don't show again" checkbox for anyone who wants to opt out of future auto-opens entirely.

Two independent flags, not one

initVisibility() checks two separate localStorage keys: wnc_last_seen_version (which version, if any, this visitor has already dismissed) and wnc_muted (whether they've explicitly opted out forever). The modal only auto-opens when isNewVersion is true *and* muted is false — both conditions matter independently. A visitor who dismisses without checking the box will still see the *next* version's changelog; a visitor who checks the box won't see any future version until wnc_muted is cleared, even across version bumps.

A quiet notification dot, then a delayed auto-open

Rather than instantly popping the modal open the moment the page loads (jarring, and easy to dismiss reflexively without reading), initVisibility() first lights up a small red dot on the trigger button, then opens the modal after a 500ms setTimeout. That half-second gives the page a moment to settle visually before the modal appears, and the dot itself remains a permanent, low-pressure signal that "there's something new here" for a visitor who closes the auto-opened modal without reading closely.

`CURRENT_VERSION` is the only thing you change on release

Bumping CURRENT_VERSION to a new string (e.g. '4.3') is the entire mechanism for making the modal reappear for everyone — lastSeen !== CURRENT_VERSION becomes true again for every visitor whose stored value still reads the old version, regardless of whether they'd previously dismissed an older release. There's no need to clear anyone's storage or add version-specific logic elsewhere.

Reading and writing `localStorage` defensively

readStorage() and writeStorage() both wrap their calls in try/catch — private browsing modes, disabled storage, or a full storage quota can all make localStorage throw rather than simply fail silently. Wrapping every access means a storage failure degrades to "the modal simply auto-opens every time" rather than throwing an uncaught error that could break the rest of the page's script execution.

Entries categorized, not just listed

Each changelog entry carries a small colored label — NEW, IMPROVED, or FIXED — so a visitor scanning quickly can distinguish a net-new capability from a bug fix without reading every description in full. This is a deliberate content structure choice as much as a visual one: writing real changelog entries in three buckets makes the list easier to write consistently release after release, too.

Customizing it

Change CURRENT_VERSION on every release and update the .wnc-list entries to match. If you want the modal to be dismissible without ever auto-reopening for *any* version (a pure "view changelog on demand" trigger with no auto-open behavior), delete the initVisibility() call at the bottom — the click-to-open trigger button keeps working entirely independently of the auto-open logic.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Instead of guessing why the modal sometimes auto-opens and sometimes doesn't, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the two independent localStorage flags — the last-seen version string and the permanent mute flag — interact in initVisibility() to decide whether to auto-open, and why bumping a single CURRENT_VERSION constant is enough to make the modal reappear for everyone who hasn't muted it. The same assistant can help you extend it — ask it to fetch the changelog entries and current version from a real API instead of hardcoding them in the HTML, add a small "view full changelog" link that opens a dedicated changelog page for entries older than the current release, or sync the "seen" state to a real user account via your backend instead of per-browser localStorage so it follows a user across devices. It's also useful for a UX review: ask whether a 500ms auto-open delay is the right amount of friction for your product's tone, or whether the notification dot alone (with no auto-open at all) would respect returning users' attention better. Treat the code less like a finished artifact and more like a starting point for a conversation.

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 "what's new" changelog modal in plain HTML, CSS, and vanilla JavaScript that auto-opens at most once per release using localStorage — no framework, no backend.

Requirements:
- A visible trigger button that can open the modal manually at any time, plus a small notification dot on that button that appears when there's an unseen release.
- A modal (backdrop + centered dialog with a fade/scale transition) listing several changelog entries, each with a small colored category label (e.g. NEW, IMPROVED, FIXED), a short title, and a one-sentence description.
- Track a version string constant in the JavaScript. On page load, compare it against a version string previously saved to localStorage (if any) to determine whether this is a version the current browser has already acknowledged.
- If the current version has not yet been acknowledged AND a separate "muted" flag (also read from localStorage) is not set, automatically open the modal after a short delay (not instantly on load) and show the notification dot.
- The modal's footer must include a "Got it" button that saves the current version string to localStorage (marking it as seen) and closes the modal, plus a "don't show this again" checkbox that, if checked when "Got it" is clicked, sets the separate permanent mute flag so no future version's changelog auto-opens until that flag is cleared — dismissing without checking the box must NOT set the mute flag, only the per-version seen state.
- Wrap all localStorage reads and writes in error handling so the modal still functions (falling back to always auto-opening) if storage access throws, such as in a private browsing context.
- Support closing the modal via a close button, backdrop click, and the Escape key, independent of the "Got it" button's save-and-close behavior.

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 pageSince this version hasn't been seen yet, a notification dot appears and the modal auto-opens after a brief delay.
  2. 2
    Read the changelog entriesEach entry is labeled NEW, IMPROVED, or FIXED for quick scanning.
  3. 3
    Click "Got it"The current version is saved to localStorage — reloading the page will not reopen the modal automatically again.
  4. 4
    Try "Don't show this again"Check the box before clicking "Got it" to mute all future auto-opens, even for later versions.
  5. 5
    Bump the version to test againChange CURRENT_VERSION in the JS panel and reload — the modal auto-opens again since it's "new."
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

SaaS product release announcements
Surface real release notes to active users exactly once per version, without repeated interruptions.
Internal tools and admin dashboards
Keep a team informed of internal tool changes without needing a separate announcement channel.
Learn version-gated localStorage patterns
Study how comparing a stored version string against a current constant drives "show once per release" logic.
Browser extension and desktop app updates
Reuse the same auto-open-once pattern for a post-update "what changed" screen.
Beta and feature-flag rollout announcements
Pair with a feature flag toggle panel to announce a newly enabled capability.
Related: Changelog Feed
Pair with the Changelog Feed for a persistent, scrollable full history alongside this per-version popup.

Got questions?

Frequently Asked Questions

No — it only auto-opens when the visitor has not yet seen the current CURRENT_VERSION, tracked via a localStorage key. Once "Got it" is clicked, that version string is saved, so reloading the page (or returning later, as long as CURRENT_VERSION has not changed) will not trigger another automatic open.

Dismissing without checking the box only records that this specific version has been seen — the visitor will still see the auto-open for the NEXT version whenever you bump CURRENT_VERSION. Checking "don't show this again" sets a separate permanent mute flag that suppresses all future auto-opens regardless of version, until that flag is cleared.

Change the CURRENT_VERSION constant to a new string (e.g. from "4.2" to "4.3") and update the changelog entries in the HTML to match. Every visitor whose stored "last seen" value still reads the old version string will have the modal auto-open again — unless they had previously checked "don't show this again."

Popping a modal open the instant the page loads tends to feel jarring and gets dismissed reflexively without being read. A small notification dot appears immediately as a low-pressure signal, and the modal itself opens after a brief setTimeout delay so the page has a moment to settle visually first.

Both the read and write helper functions wrap their localStorage calls in try/catch. If storage access throws, the read simply returns null (treating every version as unseen) and the write silently does nothing — the practical result is that the modal auto-opens on every visit rather than the page throwing an uncaught error that could break other scripts.

Yes. The visible trigger button's click handler calls openModal() directly and is completely independent of the auto-open/localStorage logic, so a visitor can reopen the changelog on demand at any time regardless of whether they have already seen or muted the current version.