SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/notifications

8 CSS Notifications and Alerts

3 Pure CSS5 with JSPublished 2026-10-04Free · no sign-up

Eight ways to tell people something happened: stacked toasts, inline success, info, warning and error alerts, a dismissible announcement banner, a notification bell with a panel, an undo toast with a pausable timer, an activity feed, a snackbar and a soft permission prompt.

01. Toast Notification Stack

ToastLive region

Toasts that slide in at the corner, stack on top of each other and disappear after five seconds. They are added to a polite live region so screen readers announce each message, and each toast has a close button for people who need more time.

View code
<button id="nt-add">Save changes</button>
<ol class="toasts" aria-live="polite" aria-label="Notifications"></ol>
#nt-add { padding: 10px 16px; border: 0; border-radius: 10px; background: #7c9cff; color: #0e1118; font: 700 14px Inter, system-ui, sans-serif; cursor: pointer; }
#nt-add:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.toasts { position: fixed; right: 16px; bottom: 16px; display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; font: 14px Inter, system-ui, sans-serif; }
.toasts li { display: flex; align-items: center; gap: 12px; min-width: 240px; padding: 12px 12px 12px 14px; border: 1px solid #2c3346; border-left: 4px solid #34d399; border-radius: 10px; background: #12161f; color: #e7ebf5; animation: nt-in .25s ease-out; }
.toasts button { margin-left: auto; width: 26px; height: 26px; border: 0; border-radius: 6px; background: #1e2433; color: #c9d1e2; cursor: pointer; }
.toasts button:focus-visible { outline: 2px solid #34d399; }
@keyframes nt-in { from { opacity: 0; transform: translateY(10px); } }
@media (prefers-reduced-motion: reduce) { .toasts li { animation: none; } }
const list = document.querySelector('.toasts');
let n = 0;
function toast(msg) {
  const li = document.createElement('li');
  li.innerHTML = '<span></span><button aria-label="Dismiss notification">&times;</button>';
  li.firstChild.textContent = msg;
  li.querySelector('button').addEventListener('click', () => li.remove());
  list.appendChild(li);
  setTimeout(() => li.remove(), 5000);
}
document.getElementById('nt-add').addEventListener('click', () => toast('Changes saved (' + (++n) + ')'));
toast('Welcome back, Sam');

02. Inline Alert Messages

AlertStatus

Four inline alerts for success, information, warning and error, each with an icon, a short title and a sentence. The type is spelled out in the title as well as shown by colour, and the icons are decorative so they are hidden from screen readers.

View code
<div class="alerts">
  <div class="al ok"><span aria-hidden="true">&#10003;</span><p><b>Saved.</b> Your shop settings are up to date.</p></div>
  <div class="al info"><span aria-hidden="true">i</span><p><b>Note:</b> Prices will include tax from 1 November.</p></div>
  <div class="al warn"><span aria-hidden="true">!</span><p><b>Warning:</b> Only 3 units left of the linen tote.</p></div>
  <div class="al err" role="alert"><span aria-hidden="true">&times;</span><p><b>Error:</b> Payment failed. Check the card details.</p></div>
</div>
.alerts { display: grid; gap: 10px; width: min(440px, 92vw); font: 14px/1.5 Inter, system-ui, sans-serif; }
.al { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border: 1px solid; border-radius: 12px; }
.al span { flex: none; display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; color: #fff; font-weight: 800; font-size: 13px; }
.al p { margin: 0; }
.ok { background: #ecfdf3; border-color: #abefc6; color: #054f31; } .ok span { background: #079455; }
.info { background: #eff8ff; border-color: #b2ddff; color: #194185; } .info span { background: #1570ef; font-family: Georgia, serif; }
.warn { background: #fffaeb; border-color: #fedf89; color: #7a2e0e; } .warn span { background: #dc6803; }
.err { background: #fef3f2; border-color: #fecdca; color: #7a271a; } .err span { background: #d92d20; }

03. Dismissible Top Banner

BannerAnnouncement

A full-width announcement bar for sales or service news, with a link and a close button. The dismissal is remembered in localStorage so the banner does not return on every page, and the close button has a clear accessible name.

View code
<div class="bn" id="bn" role="region" aria-label="Announcement">
  <p>Free shipping on every order this weekend. <a href="#">Shop now</a></p>
  <button aria-label="Dismiss announcement">&times;</button>
</div>
.bn { box-sizing: border-box; max-width: 100%; display: flex; align-items: center; gap: 12px; width: min(560px, 94vw); padding: 10px 12px 10px 18px; border-radius: 12px; background: linear-gradient(90deg, #6d28d9, #db2777); color: #fff; font: 600 14px Inter, system-ui, sans-serif; }
.bn p { flex: 1; margin: 0; }
.bn a { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
.bn button { width: 30px; height: 30px; border: 0; border-radius: 8px; background: rgba(255,255,255,.18); color: #fff; font-size: 18px; cursor: pointer; }
.bn a:focus-visible, .bn button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
const bn = document.getElementById('bn');
try { if (localStorage.getItem('bn-closed')) bn.hidden = true; } catch (e) {}
bn.querySelector('button').addEventListener('click', () => { bn.hidden = true; try { localStorage.setItem('bn-closed', '1'); } catch (e) {} });

04. Notification Bell with Panel

HeaderBadge

A bell icon with an unread count that opens a panel of recent notifications. The button's name includes the number, such as Notifications, 3 unread, unread items are marked with a dot and hidden text, and Mark all as read clears them.

View code
<div class="bell">
  <button class="bb" aria-expanded="true" aria-controls="bell-p" aria-label="Notifications, 3 unread"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 16V11a6 6 0 1112 0v5l2 2H4zM10 20a2 2 0 004 0"/></svg><span class="ct" aria-hidden="true">3</span></button>
  <div id="bell-p" class="bp">
    <header><b>Notifications</b><button class="mark">Mark all as read</button></header>
    <ul>
      <li class="new"><span class="sr">Unread: </span>New order #1048 from Lagos</li>
      <li class="new"><span class="sr">Unread: </span>Review received: 5 stars</li>
      <li class="new"><span class="sr">Unread: </span>Payout of $420 sent</li>
      <li>Low stock: speckled mug</li>
    </ul>
  </div>
</div>
.bell { position: relative; width: 300px; display: grid; justify-items: end; font-family: Inter, system-ui, sans-serif; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.bb { position: relative; width: 42px; height: 42px; border: 1.5px solid #d0d5dd; border-radius: 12px; background: #fff; cursor: pointer; }
.bb svg { width: 22px; height: 22px; fill: none; stroke: #101828; stroke-width: 1.8; stroke-linejoin: round; }
.ct { position: absolute; top: -6px; right: -6px; min-width: 18px; height: 18px; padding: 0 4px; border: 2px solid #fff; border-radius: 999px; background: #d92d20; color: #fff; font: 700 11px/14px Inter, sans-serif; }
.bb:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.bp { width: 100%; margin-top: 8px; border: 1px solid #e4e7ec; border-radius: 14px; background: #fff; box-shadow: 0 16px 32px -16px rgba(16,24,40,.35); }
.bp header { display: flex; justify-content: space-between; align-items: center; padding: 12px 14px; border-bottom: 1px solid #eef0f4; color: #101828; font-size: 14px; }
.mark { border: 0; background: none; color: #1d4ed8; font: 600 13px Inter, sans-serif; cursor: pointer; }
.bp ul { margin: 0; padding: 6px; list-style: none; }
.bp li { position: relative; padding: 9px 10px 9px 22px; border-radius: 8px; color: #475467; font-size: 13.5px; }
.bp li.new { color: #101828; font-weight: 600; }
.bp li.new::before { content: ""; position: absolute; left: 8px; top: 15px; width: 7px; height: 7px; border-radius: 50%; background: #2563eb; }
const bb = document.querySelector('.bb'), bp = document.getElementById('bell-p');
bb.addEventListener('click', () => { const o = bp.hidden; bp.hidden = !o; bb.setAttribute('aria-expanded', String(o)); });
document.querySelector('.mark').addEventListener('click', () => {
  bp.querySelectorAll('li.new').forEach(li => { li.classList.remove('new'); li.querySelector('.sr').remove(); });
  bb.querySelector('.ct').remove(); bb.setAttribute('aria-label', 'Notifications, none unread');
});

05. Toast with Undo and Timer

UndoToast

A deleted-item toast with an Undo button and a thin bar that shrinks to show how long remains. Hovering or focusing the toast pauses the timer, which follows the WCAG advice to let people control time limits on messages they need to act on.

View code
<div class="ut" role="status">
  <p>Product moved to trash</p>
  <button>Undo</button>
  <span class="tm" aria-hidden="true"></span>
</div>
.ut { box-sizing: border-box; max-width: 100%; position: relative; display: flex; align-items: center; gap: 16px; width: min(340px, 90vw); padding: 14px 16px; overflow: hidden; border-radius: 12px; background: #f9fafb; color: #101828; font: 600 14px Inter, system-ui, sans-serif; box-shadow: 0 16px 30px -14px #000; }
.ut p { flex: 1; margin: 0; }
.ut button { border: 0; background: none; color: #1d4ed8; font: 800 14px Inter, sans-serif; cursor: pointer; }
.ut button:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.tm { position: absolute; left: 0; bottom: 0; height: 3px; width: 100%; background: #2563eb; transform-origin: left; animation: ut-t 6s linear forwards; }
.ut:hover .tm, .ut:focus-within .tm { animation-play-state: paused; }
@keyframes ut-t { to { transform: scaleX(0); } }
const ut = document.querySelector('.ut'), tm = ut.querySelector('.tm');
tm.addEventListener('animationend', () => ut.querySelector('p').textContent = 'Deleted permanently');
ut.querySelector('button').addEventListener('click', () => { tm.style.animation = 'none'; ut.querySelector('p').textContent = 'Product restored'; });

06. Notification Feed Items

FeedAvatar

A list of activity notifications with avatars, bold actor names, a time stamp in a time element and an unread highlight. Times use the datetime attribute so they are machine-readable, and the list is a plain ul that screen readers can count.

View code
<ul class="feed">
  <li class="un"><span class="av" style="background:#f97316" aria-hidden="true">JL</span><p><b>Jun Li</b> commented on <a href="#">Spring catalogue</a><time datetime="2026-10-04T09:12">12 min ago</time></p></li>
  <li class="un"><span class="av" style="background:#0ea5e9" aria-hidden="true">MO</span><p><b>Mira O.</b> invited you to <a href="#">Studio team</a><time datetime="2026-10-04T08:40">45 min ago</time></p></li>
  <li><span class="av" style="background:#22c55e" aria-hidden="true">RB</span><p><b>Ravi B.</b> approved your draft<time datetime="2026-10-03T17:05">Yesterday</time></p></li>
</ul>
.feed { box-sizing: border-box; max-width: 100%; width: min(400px, 92vw); margin: 0; padding: 6px; list-style: none; border-radius: 14px; background: #fff; box-shadow: 0 8px 24px -12px rgba(16,24,40,.25); font: 14px/1.45 Inter, system-ui, sans-serif; color: #475467; }
.feed li { display: flex; gap: 12px; padding: 12px; border-radius: 10px; }
.feed li.un { background: #f5f8ff; }
.av { flex: none; display: grid; place-items: center; width: 38px; height: 38px; border-radius: 50%; color: #fff; font-weight: 800; font-size: 13px; }
.feed p { margin: 0; }
.feed b { color: #101828; }
.feed a { color: #1d4ed8; font-weight: 600; text-decoration: none; }
.feed a:hover { text-decoration: underline; }
.feed time { display: block; color: #5d6679; font-size: 12.5px; }

07. Bottom Snackbar with Action

MobileSnackbar

A mobile-style snackbar centred at the bottom of the screen with one message and one action, such as Retry after a failed upload. It rises into view from below, stays until dismissed, and keeps a single action so the choice is simple.

View code
<div class="snk" role="alert">
  <p>Upload failed. Check your connection.</p>
  <button>Retry</button>
</div>
.snk { position: fixed; left: 50%; bottom: 20px; display: flex; align-items: center; gap: 18px; width: max-content; max-width: calc(100vw - 32px); padding: 12px 12px 12px 18px; border-radius: 10px; background: #f2f4f7; color: #101828; font: 500 14px Inter, system-ui, sans-serif; transform: translateX(-50%); box-shadow: 0 12px 30px -10px #000; animation: snk-in .3s ease-out; }
.snk p { margin: 0; }
.snk button { padding: 6px 10px; border: 0; border-radius: 6px; background: none; color: #6d28d9; font: 800 14px Inter, sans-serif; text-transform: uppercase; letter-spacing: .04em; cursor: pointer; }
.snk button:focus-visible { outline: 2px solid #6d28d9; }
@keyframes snk-in { from { transform: translate(-50%, 120%); } }
@media (prefers-reduced-motion: reduce) { .snk { animation: none; } }

08. Permission Prompt Card

PromptOnboarding

A soft prompt that explains why notifications are useful before the browser's own permission dialog appears, so the real prompt is only shown to people who said yes. Not now is as easy to press as Turn on, which avoids nagging.

View code
<div class="pp" role="region" aria-labelledby="pp-t">
  <span class="ic" aria-hidden="true">&#128276;</span>
  <div><h3 id="pp-t">Get order updates?</h3><p>We will only notify you when an order ships or a customer messages you.</p>
    <div class="acts"><button class="no">Not now</button><button class="yes">Turn on</button></div></div>
</div>
.pp { box-sizing: border-box; max-width: 100%; display: flex; gap: 14px; width: min(400px, 92vw); padding: 18px; border: 1px solid #e4e7ec; border-radius: 16px; background: #fff; font-family: Inter, system-ui, sans-serif; }
.ic { flex: none; display: grid; place-items: center; width: 42px; height: 42px; border-radius: 12px; background: #eef4ff; font-size: 20px; }
.pp h3 { margin: 0 0 4px; color: #101828; font-size: 16px; }
.pp p { margin: 0 0 12px; color: #475467; font-size: 14px; line-height: 1.5; }
.acts { display: flex; gap: 8px; }
.acts button { padding: 8px 14px; border-radius: 9px; font: 700 13.5px Inter, sans-serif; cursor: pointer; }
.no { border: 1.5px solid #d0d5dd; background: #fff; color: #344054; }
.yes { border: 0; background: #2563eb; color: #fff; }
.acts button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
document.querySelector('.no').addEventListener('click', () => document.querySelector('.pp').hidden = true);
document.querySelector('.yes').addEventListener('click', () => { if ('Notification' in window) Notification.requestPermission(); document.querySelector('.pp').hidden = true; });

About these CSS Notifications and Alerts

Live regions

Messages that appear without a page load must be announced. Put toasts in a container with aria-live="polite", and use role="alert" only for urgent errors that should interrupt.

Give people time

Toasts that vanish after a few seconds can disappear before someone reads them. Pause the timer on hover and focus, provide a close button, and never put the only copy of important information in a toast.

Not colour alone

Success, warning and error styles need words or icons as well as colour, such as starting the message with Error. The alert demo uses a title word and an icon for each type.

Asking for permission

Explain the benefit before triggering the browser's own notification prompt, and only ask when someone says yes. The permission demo makes Not now just as easy as Turn on.

Frequently asked questions

Toast or inline alert?

Use toasts for brief confirmations of something the person did, and inline alerts for messages tied to a part of the page, such as a form error.

Which demos need JavaScript?

The toast stack, banner, bell, undo toast and permission prompt.

How long should a toast stay?

At least five seconds, longer for messages with an action, and it should pause while hovered or focused.