8pt

Notification Bell

Notification Bell•Feedback
Overview

When a new message arrives the bell swings and a badge pops up with the count; click to open the notification panel and mark everything as read.

Plain HTML, CSS and a little vanilla JS, with no framework dependency. Copy the prompt into Claude Code, Cursor, Trae or Codex and the model adds Notification Bell to your project in your colors.

Where to use it

Notifications, hints, loading and status feedback, so people know whether an action worked.

Install

With Builder, install it with the 8pt CLI or call it directly from an editor connected to 8pt MCP.

$ npm i -g @8pt-dev/cli && 8pt add notification-bell
mcp › Use 8pt MCP to call get_component("notification-bell")

Code

Source for Notification Bell. Motion uses only transform and opacity and respects the Reduce motion setting.

<div class="x-bell open">
  <button type="button" class="x-bell__btn" aria-label="Notifications" aria-expanded="true">
    <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 1 1 12 0c0 7 3 8 3 8H3s3-1 3-8"/><path d="M10.3 20a1.9 1.9 0 0 0 3.4 0"/></svg>
    <span class="x-bell__badge">0</span>
  </button>
  <div class="x-bell__panel" role="dialog" aria-label="Notification list">
    <header><b>Notifications</b><button type="button" class="x-bell__read">Mark all as read</button></header>
    <ul></ul>
  </div>
</div>
.x-bell{position:relative;display:grid;justify-items:center;width:min(300px,100%);height:210px;color:var(--d-fg,#fff)}
.x-bell__btn{position:relative;display:grid;place-items:center;width:44px;height:44px;border-radius:12px;border:1px solid var(--d-line,rgba(255,255,255,.12));background:var(--d-card,#14141c);color:inherit;cursor:pointer}
.x-bell__btn svg{transform-origin:50% 10%}
.x-bell__btn.ring svg{animation:x-bell-ring .8s cubic-bezier(.36,.07,.19,.97)}
@keyframes x-bell-ring{0%,100%{transform:rotate(0)}15%{transform:rotate(18deg)}30%{transform:rotate(-15deg)}45%{transform:rotate(11deg)}60%{transform:rotate(-7deg)}75%{transform:rotate(3deg)}}
.x-bell__badge{position:absolute;right:-6px;top:-6px;min-width:20px;height:20px;padding:0 5px;border-radius:999px;border:2px solid var(--d-bg,#09090d);background:#ff5a5f;color:#fff;font:700 10.5px/16px var(--d-font,system-ui,sans-serif);text-align:center;font-variant-numeric:tabular-nums;transform:scale(0);transition:transform .35s cubic-bezier(.3,1.6,.5,1)}
.x-bell__badge.on{transform:scale(1)}
.x-bell__badge.pop{animation:x-bell-pop .35s cubic-bezier(.3,1.6,.5,1)}
@keyframes x-bell-pop{50%{transform:scale(1.3)}}
.x-bell__panel{position:absolute;top:54px;left:0;right:0;border-radius:14px;background:var(--d-card,#14141c);border:1px solid var(--d-line,rgba(255,255,255,.12));box-shadow:0 22px 44px -22px rgba(0,0,0,.7);opacity:0;transform:translateY(-6px) scale(.98);transform-origin:top center;pointer-events:none;transition:opacity .2s,transform .25s cubic-bezier(.2,.8,.2,1)}
.x-bell.open .x-bell__panel{opacity:1;transform:none;pointer-events:auto}
.x-bell__panel header{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border-bottom:1px solid var(--d-line,rgba(255,255,255,.1));font:13px/1 var(--d-font,system-ui,sans-serif)}
…
const init = (root) => {
  const wrap = root.querySelector('.x-bell'), btn = wrap.querySelector('.x-bell__btn'), badge = wrap.querySelector('.x-bell__badge'), ul = wrap.querySelector('ul');
  const msgs = [['Ava commented on your component', '2 min ago'], ['Build #482 passed', '5 min ago'], ['Leo invited you to the Atlas project', '12 min ago'], ['New theme Dusk is live', '1 hour ago']];
  let n = 0, unread = 0, timer;
  const draw = () => { badge.textContent = unread > 9 ? '9+' : unread; badge.classList.toggle('on', unread > 0); };
  const push = () => {
    const [t, s] = msgs[n++ % msgs.length], li = document.createElement('li');
    li.innerHTML = `<i></i><div>${t}<small>${s}</small></div>`; ul.prepend(li); if (ul.children.length > 4) ul.lastElementChild.remove();
    unread++; draw(); badge.classList.remove('pop'); void badge.offsetWidth; badge.classList.add('pop');
    btn.classList.remove('ring'); void btn.offsetWidth; btn.classList.add('ring');
  };
  btn.addEventListener('click', () => { const o = wrap.classList.toggle('open'); btn.setAttribute('aria-expanded', String(o)); });
…
Subscribe to Builder to see the full source.See pricing

More feedback components

View all

Toast Stack

More components

View all

AI Prompt Composer

Film Strip Carousel

Auto Tab Showcase

Media Bento Grid

Agent Chat Window

Model Pill Marquee

Director Art Cards

Mac File Window

Keep exploring