Source Code

<div class="sab-page">
  <div class="sab-banner" id="sabBanner">
    <button class="sab-close" id="sabClose" aria-label="Dismiss">&times;</button>
    <div class="sab-icon" aria-hidden="true">FT</div>
    <div class="sab-info">
      <p class="sab-name">FwdTools</p>
      <p class="sab-meta">★★★★★ 4.9 · Free · On the App Store</p>
    </div>
    <button class="sab-open" id="sabOpen">Open</button>
  </div>

  <div class="sab-content">
    <h1>Free UI Snippets</h1>
    <p class="sab-lede">Copy-paste HTML, CSS &amp; JS components for your next project.</p>
    <div class="sab-cards">
      <div class="sab-card"></div>
      <div class="sab-card"></div>
      <div class="sab-card"></div>
      <div class="sab-card"></div>
    </div>
    <button class="sab-reopen" id="sabReopen" hidden>Show app banner</button>
  </div>
</div>

Smart App Banner — Free HTML CSS JS Mobile Web Snippet

Smart App Banner · Mobile · Plain HTML, CSS & JS · Live preview

What's included

Features

Timestamp-based dismissal
A stored future timestamp lets the banner reappear automatically after a cooldown, not just vanish forever.
try/catch-wrapped storage
Both reading and writing localStorage are wrapped so private browsing or storage restrictions degrade gracefully.
Visible re-open control
A small button remains in the page after dismissal so visitors aren't stuck waiting out the cooldown.
In-flow layout, not an overlay
The banner occupies real space at the top instead of floating over page content.
Slide-down entrance animation
A CSS keyframe gives the banner a deliberate, non-jarring appearance.
App icon and store rating mockup
Matches the visual language of Safari's native smart app banner.
Configurable cooldown length
One constant controls how many days a dismissal lasts before re-prompting.
Zero dependencies
Pure HTML, CSS, and vanilla JavaScript — no cookie or storage library.

About this UI Snippet

Smart App Banner — Dismissible Prompt with a Persisted Cooldown

Screenshot of the Smart App Banner snippet rendered live

A smart app banner is the thin strip at the top of a mobile website nudging visitors toward a native app — Safari has a built-in version driven by a meta tag, and this snippet reproduces the same UX pattern as a fully custom, cross-browser component with its own dismissal memory.

A cooldown timestamp, not a boolean flag

Dismissing the banner doesn't just set a "dismissed: true" flag — it stores a future timestamp: Date.now() + DISMISS_DAYS * 24 * 60 * 60 * 1000. isDismissed() then simply checks Date.now() < Number(until). This means the banner reappears automatically after the cooldown window passes, rather than being permanently gone after one dismissal — a boolean can't expire on its own, but a stored timestamp compared against the current time can.

localStorage wrapped in try/catch, both directions

Both safeGet() and safeSet() wrap their localStorage calls in try/catch. Safari's private browsing mode and some locked-down environments throw on localStorage.setItem() even though the API exists, and third-party storage restrictions can block access entirely. Catching the error means the banner still functions perfectly as a same-session UI element — it just won't remember the dismissal across a reload in those environments, a reasonable and honest fallback rather than a broken page.

A visible way back in, not a silent disappearance

Once dismissed, a small "Show app banner" button appears in the page content (reopenBtn.hidden = false) so a visitor who changes their mind isn't stuck waiting out the cooldown — clicking it calls safeSet(STORAGE_KEY, '0'), a timestamp in the past, so isDismissed() evaluates false again immediately.

A slide-down entrance that respects layout

@keyframes sabSlideDown animates the banner in from translateY(-100%), and because the banner occupies real space at the top of the flow (not position: fixed overlapping content), the page content shifts down naturally rather than the banner floating over and obscuring the first thing a visitor sees.

Customizing it

Replace the Open button's placeholder with a real custom URL scheme deep link (e.g. fwdtools://open) that falls back to the store listing via a timed redirect if the app isn't installed, and adjust DISMISS_DAYS to match how aggressively you want to re-prompt.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to work out the storage fallback logic by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain why the dismissal is stored as a future timestamp instead of a simple boolean, and why both the read and write paths to localStorage are wrapped in try/catch rather than just checking if the API exists. The same assistant can help optimize it too — ask whether the banner should check a User-Agent or platform detection API to only show on genuinely mobile devices. It's also useful for extending it: ask it to wire the Open button to a real custom URL scheme with a store-redirect fallback timer, add a PWA install-prompt variant, or animate the reopen button's appearance. 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 "smart app banner" for a mobile web page in plain HTML, CSS, and JavaScript with no framework or library.

Requirements:
- A banner docked at the top of the page's normal content flow (not a fixed overlay) showing an app icon, app name, a star rating line, and an "Open" button, plus a small close button.
- Clicking close hides the banner and stores a dismissal timestamp set to a configurable number of days in the future, wrapping the storage write in a try/catch so it degrades gracefully if localStorage throws (as it can in Safari private browsing).
- On page load, check whether a stored dismissal timestamp exists and is still in the future (also wrapped in try/catch with a safe fallback); if so, keep the banner hidden without any animation.
- After dismissal, show a small persistent "Show app banner" button elsewhere in the page that, when clicked, clears the stored dismissal and brings the banner back immediately.
- The banner's appearance should use a CSS slide-down keyframe animation from above the viewport.
- The Open button should show a brief "Opening…" loading state before reverting, standing in for a real native app deep link with a store-page fallback in production.

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
    Paste HTML, CSS, and JSA banner renders at the top of the mock page prompting the visitor to open the app.
  2. 2
    Click the close buttonThe banner hides and a "Show app banner" button appears in the page content below.
  3. 3
    Reload the previewIn a real deployment, the dismissal persists via localStorage — the banner stays hidden until the cooldown expires.
  4. 4
    Click "Show app banner"Immediately clears the stored dismissal and brings the banner back.
  5. 5
    Adjust the cooldownIn the JS, edit the DISMISS_DAYS constant to control how long a dismissal lasts before the banner reappears.
  6. 6
    Wire up the Open buttonReplace the placeholder timeout with a real custom URL scheme deep link and an App/Play Store fallback redirect.

Real-world uses

Common Use Cases

Mobile web to native app conversion
Nudge mobile web visitors toward your native app without an intrusive full-screen interstitial.
Cross-browser smart banners
Safari's built-in banner only works in Safari — this pattern works identically in Chrome, Firefox, and others.
Progressive Web App install prompts
Adapt the same banner to trigger a PWA's beforeinstallprompt flow instead of a store link.
Seasonal or campaign banners
Reuse the dismiss-with-cooldown pattern for any top-of-page announcement that shouldn't reappear every visit.
A/B testing app conversion prompts
Swap in different banner copy or CTAs while keeping the same dismissal persistence logic.
Learning localStorage fallback patterns
A clear example of wrapping browser storage APIs defensively for private-mode compatibility.
Related: Multi-Touch Gesture Visualizer
See the Multi-Touch Gesture Visualizer for a related mobile pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

A boolean flag has no way to expire — once set, the banner would stay hidden forever with no built-in mechanism to re-prompt later. Storing Date.now() plus a cooldown duration lets isDismissed() simply compare the current time against that stored value, so the banner naturally reappears once the cooldown window passes without any extra logic.

Both safeGet() and safeSet() wrap their localStorage calls in try/catch. If either throws, the function silently falls back — reads return null (treated as "not dismissed") and writes are simply skipped. The banner still displays and can be dismissed for the current page view; it just won't remember that dismissal on the next visit in that environment.

Without it, a visitor who dismissed the banner by accident, or who changes their mind, would have no way to bring it back except waiting out the full cooldown period. The reopen button calls safeSet() with a past timestamp, which makes isDismissed() immediately return false again.

Set window.location.href to your app's custom URL scheme (like fwdtools://open) or, for iOS, use a Universal Link. Since there's no reliable way to detect whether the app opened, the common pattern is to also start a timer that redirects to the App Store or Play Store listing if the page is still visible after roughly 1–2 seconds, implying the deep link failed.

Safari's native version is triggered by a apple-itunes-app meta tag and only renders in Safari on iOS — it can't be styled and doesn't work in other browsers. This snippet is a fully custom component that renders identically everywhere, can be styled to match your brand, and gives you full control over the dismissal and re-prompt behavior.