webdevpuneet.com

Notification Bell — Free HTML CSS JS Dropdown Snippet

Notification Bell · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

Copy & paste

Source Code

<div class="page">
  <div class="bell-wrap">
    <button class="bell-btn" id="bell" onclick="toggle()">
      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
      <span class="badge" id="badge">3</span>
    </button>

    <div class="notif-panel" id="panel">
      <div class="notif-head">
        <span class="notif-title">Notifications</span>
        <button class="mark-all" onclick="markAll()">Mark all read</button>
      </div>
      <div class="notif-list" id="notif-list">
        <div class="notif unread" data-id="1">
          <div class="notif-avatar" style="background:#6366f1">PS</div>
          <div class="notif-body">
            <div class="notif-text"><strong>Puneet</strong> commented on your post</div>
            <div class="notif-time">2 min ago</div>
          </div>
          <div class="notif-dot"></div>
        </div>
        <div class="notif unread" data-id="2">
          <div class="notif-avatar" style="background:#10b981">💰</div>
          <div class="notif-body">
            <div class="notif-text">Invoice <strong>#1042</strong> was paid — $2,400</div>
            <div class="notif-time">1 hour ago</div>
          </div>
          <div class="notif-dot"></div>
        </div>
        <div class="notif unread" data-id="3">
          <div class="notif-avatar" style="background:#f59e0b">⚠️</div>
          <div class="notif-body">
            <div class="notif-text">Storage at <strong>90%</strong> — upgrade your plan</div>
            <div class="notif-time">3 hours ago</div>
          </div>
          <div class="notif-dot"></div>
        </div>
        <div class="notif" data-id="4">
          <div class="notif-avatar" style="background:#8b5cf6">🚀</div>
          <div class="notif-body">
            <div class="notif-text">Deployment <strong>v2.4.1</strong> succeeded</div>
            <div class="notif-time">Yesterday</div>
          </div>
        </div>
      </div>
      <div class="notif-foot">
        <a href="#" class="view-all">View all notifications →</a>
      </div>
    </div>
  </div>
</div>

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.

· Become a supporter

What's included

Features

Absolute-positioned red unread badge with count number
toggle() adds/removes .open on the panel — one function for open and close
opacity + translateY(-8px) CSS transition on .panel.open
pointer-events: none when closed — invisible panel does not intercept clicks
.unread class drives all visual differences per notification item
markAll() removes .unread, removes .notif-dot, hides badge when count reaches zero
Click-outside close via e.target.closest(".bell-wrap") document listener
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons
Live split-pane editor — preview updates as you type

About this UI Snippet

Notification Bell — Unread Badge, Toggle Dropdown & Mark All Read

Screenshot of the Notification Bell snippet rendered live

A notification bell in the app header gives users a quick glance at pending alerts without navigating away from their current view — the full list typically lives in a notification center. The red unread badge communicates urgency; the dropdown reveals the notification list; Mark all read clears the badge. This is a complete, production-quality implementation with click-outside close.

The unread badge

The bell button has an absolutely positioned .badge div showing the unread count. It starts with a display: none state that can be shown conditionally. When there are unread notifications, the badge appears with a red background and white count number.

The dropdown toggle

toggle() calls panel.classList.toggle('open'). The .panel.open CSS rule transitions opacity from 0 to 1 and transform from translateY(-8px) to translateY(0). pointer-events: none when closed prevents the invisible panel from intercepting clicks. The dropdown has a fixed width and max-height with overflow-y: auto for long notification lists.

Unread notification styling

Notifications with .unread class have a blue .notif-dot indicator and a slightly tinted background. The .unread class drives all visual differences — removing it marks the notification as read.

Mark all read

markAll() queries all .notif.unread elements, removes the .unread class, removes the .notif-dot element, then hides the badge if the unread count reaches zero. This is the minimum viable implementation — in production, it would also fire an API call.

Click-outside close

A document.addEventListener('click', e => { if (!e.target.closest('.bell-wrap')) panel.classList.remove('open'); }) closes the panel when the user clicks outside. This is the same closest() pattern used in the Dropdown Menu snippet.

The badge counter

The unread badge uses position: absolute; top: -4px; right: -4px to sit above the bell icon. It shows a count number and uses min-width: 16px so it looks correct for single and double digit counts. When count exceeds 9, display "9+" instead of the full number. The badge disappears (display: none) when count reaches 0.

The dropdown open/close

Clicking the bell toggles .open on the dropdown panel, which uses scale(0.95) + opacity: 0 in the closed state and scale(1) + opacity: 1 in the open state. This CSS transition matches the popover pattern. The dropdown is positioned using position: absolute; right: 0; top: calc(100% + 8px). Click-outside detection uses document.addEventListener('click', e => { if (!e.target.closest('.bell-wrap')) close(); }).

Mark as read interaction

Each notification item has a "Mark read" button. Clicking it removes the .unread class from the notification and decrements the badge count. The mark-all-read button iterates all .unread items and marks them simultaneously. Both patterns update the badge count immediately — optimistic UI that syncs to the backend separately.

Step by step

How to Use

  1. 1
    Click the bell iconClick the bell icon in the preview to open the notification panel. Click it again or click outside to close.
  2. 2
    Click "Mark all read"Click the Mark all read link to remove the unread styling from all notifications and hide the red badge.
  3. 3
    Update notification contentIn the HTML panel, update the .notif elements with real notification text, timestamps, and icons.
  4. 4
    Add new notificationsCopy a .notif div and paste it inside the notifications list. Add .unread to mark it as new. Update the badge count.
  5. 5
    Fire an API on mark-all-readIn the JS panel, add a fetch() call inside markAll() after the DOM changes to sync the read state with your backend.
  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

Dashboard and app header notifications
Add to any dashboard header. Wire the badge count to your real-time notification API response and update it via WebSocket or polling.
Social and collaboration tools
Notify users of new mentions, comments, team activity, and messages. The .unread class and mark-all-read pattern match what users expect from social products.
Learn click-outside close pattern
The click-outside handler uses e.target.closest(".bell-wrap"). Edit the selector to understand how closest() traverses the DOM.
Prototype notification UX
Use the snippet to prototype how notification volume, grouping, and read/unread states should work before building a production notification system.
Customise the badge and panel styling
Change the badge colour, panel width, and notification item layout in the CSS panel to match your design system.
Real-time notification updates
On a WebSocket message, increment the badge count and prepend a new .notif.unread item to the list. The CSS handles the visual state automatically.
Related: Restaurant Order Status Tracker
See the Restaurant Order Status Tracker for a related dashboards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

toggle() calls panel.classList.toggle("open"). The CSS .panel.open rule transitions opacity from 0 to 1 and transform from translateY(-8px) to translateY(0) over 0.2s. pointer-events: none when closed prevents invisible click interception.

markAll() queries all .notif.unread elements. For each: it removes .unread, finds and removes the .notif-dot element inside it. After the loop, it checks if any .unread remain — if none, it hides the badge element.

A document click listener checks !e.target.closest(".bell-wrap"). If the click was outside the bell wrapper (no ancestor with that class), panel.classList.remove("open") closes the panel. This is the same pattern as the Dropdown Menu snippet.

Fetch notifications from your API and update the badge textContent with the unread count. Add .unread class to unread notification elements. Show or hide the badge based on count > 0.

Listen to a WebSocket or Server-Sent Events stream. On new notification, create a .notif div with .unread, prepend it to the notification list, increment the badge count, and show the badge if hidden.

Yes. Click "JSX" for a React component. Manage open state with useState, notifications as an array with useState, and the unread count derived from filter. Use useEffect to add and clean up the document click listener.