Bootstrap Maintenance Mode Banner — Free HTML CSS JS Snippet
Bootstrap Maintenance Mode Banner · Modals · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="container py-5 d-flex justify-content-center">
<div class="bsmaint-page">
<div class="alert alert-warning d-flex justify-content-between align-items-center mb-0 rounded-0" id="bsmaintBanner">
<span class="small">⚠ Scheduled maintenance begins in <strong id="bsmaintCountdown">--:--:--</strong>. The app may be briefly unavailable.</span>
<button type="button" class="btn-close" id="bsmaintDismiss" aria-label="Dismiss"></button>
</div>
<div class="bsmaint-body p-4 text-center">
<h6 class="fw-bold mb-1">Dashboard</h6>
<p class="small text-muted mb-3">The rest of the app underneath the banner.</p>
<button type="button" class="btn btn-sm btn-outline-secondary" id="bsmaintReload">Reload page (demo)</button>
</div>
</div>
</div>.bsmaint-page { width: 420px; max-width: 100%; border: 1px solid #eceef1; border-radius: 14px; overflow: hidden; }
.bsmaint-body { background: #fff; }const KEY = 'bsmaint-dismissed-demo';
const banner = document.getElementById('bsmaintBanner');
const countdown = document.getElementById('bsmaintCountdown');
// A fixed target a couple hours out, so the countdown has something real to
// count down to for this preview.
const target = Date.now() + 2 * 60 * 60 * 1000 + 14 * 60 * 1000;
function pad(n) { return String(n).padStart(2, '0'); }
function renderCountdown() {
const diff = Math.max(0, target - Date.now());
const h = Math.floor(diff / 3600000);
const m = Math.floor((diff % 3600000) / 60000);
const s = Math.floor((diff % 60000) / 1000);
countdown.textContent = pad(h) + ':' + pad(m) + ':' + pad(s);
}
function applyDismissedState() {
let dismissed = false;
try { dismissed = sessionStorage.getItem(KEY) === '1'; } catch (e) { /* storage unavailable — banner just always shows */ }
banner.classList.toggle('d-none', dismissed);
}
document.getElementById('bsmaintDismiss').addEventListener('click', () => {
banner.classList.add('d-none');
try { sessionStorage.setItem(KEY, '1'); } catch (e) { /* nothing to persist, banner reappears each load */ }
});
// There's no real page reload to demo inside this preview, so this button
// re-runs exactly the same check a real reload's initial page load would —
// proving the dismissal genuinely persisted rather than just hiding the DOM.
document.getElementById('bsmaintReload').addEventListener('click', applyDismissedState);
applyDismissedState();
renderCountdown();
setInterval(renderCountdown, 1000);Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Bootstrap Maintenance Mode Banner — HTML, CSS & JavaScript

This is deliberately not the same thing as a full maintenance takeover page like bootstrap-maintenance-page — that pattern replaces the entire app because it's actually down; this banner announces an *upcoming* window while the app underneath is still completely usable, which is why it's a plain dismissible Bootstrap alert sitting above normal page content rather than a takeover.
The countdown is computed fresh every second from a fixed target timestamp via renderCountdown(), converting the millisecond difference into hours/minutes/seconds with plain division and modulo — no date library needed for a straightforward countdown like this one.
Dismissal is real, persisted state, not just a hidden DOM node — clicking the close button writes to sessionStorage, guarded in try/catch the same way this collection's other storage-backed snippets are, so a blocked or unavailable storage API just means the banner reappears every load rather than breaking. Because this preview can't trigger an actual page reload, the "Reload page (demo)" button re-runs applyDismissedState() — the exact same check a fresh page load would perform — which is what proves the dismissal is genuinely being read back from storage rather than merely toggling a CSS class that a real reload would immediately undo.
Step by step
How to Use
- 1Load the snippetA warning banner shows above the app content with a live countdown ticking down every second.
- 2Interact with the app below the bannerIt's fully usable — the banner is informational, not a blocking overlay.
- 3Click the \u00d7 to dismiss the bannerIt disappears immediately, and the dismissal is saved.
- 4Click "Reload page (demo)"The banner stays hidden, proving the dismissal genuinely persisted rather than just being a one-time CSS toggle.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
sessionStorage means the dismissal only lasts for the current browser tab/session — a maintenance window is a temporary, time-bound notice, so it's reasonable for the banner to reappear in a brand new session rather than being permanently dismissed forever, the way localStorage would make it.
No — it's a normal in-flow element above the rest of the content, not an overlay or modal, so every other part of the app remains fully clickable and usable while it's showing.
This snippet stops at 00:00:00; a real implementation would typically swap the banner's message (e.g. "Maintenance in progress") or trigger the actual maintenance takeover page at that point.
Yes. Keep the target timestamp as a constant, recompute the countdown in a setInterval-driven state update (cleared on unmount), and read/write the dismissed flag from sessionStorage in the same places this snippet does.