Vanilla Breeze

notification-wc

Dual-mode notification component: dismissible banner with persistent state, or bell-icon panel with per-item read tracking and optional dynamic feed.

Overview

<notification-wc> ships two modes that share an element name. Pick with mode="banner" or mode="panel" (the default).

  • Banner — full-width, sticky strip with a dismiss button. Dismissal persists across reloads via VBStore under the namespace notifications; optional expires re-shows after N days.
  • Panel — bell-icon trigger with an unread badge. Click opens a drawer with a list of notifications composed from static <article> children and (optionally) a dynamic JSON feed. Per-item read state persists via VBStore.

Both modes work without JavaScript — banner is plain HTML; panel renders its static articles inline. JS only adds dismissal, read tracking, and dynamic fetches.

Banner Mode

A site-wide announcement strip. The persist attribute is the VBStore key under which dismissal is recorded. variant sets the color, position sticks it to the top or bottom, expires (days) lets the banner re-show after a time window.

Dismissal is recorded as vb:notifications:site-update-v3 in VBStore. Wipe the key from devtools (or call VBStore.remove('notifications', 'site-update-v3')) to test the show flow again.

Panel Mode

An ambient bell-icon trigger that opens a panel listing notifications. Static <article> children are the no-JS fallback and the source of truth for the IDs they declare; dynamic notifications fetched via src are merged on top by data-id.

Read state is persisted under VBStore namespace notifications, key read-ids. Override the namespace per instance with storage-key="…".

Dynamic feed shape

The src attribute accepts an absolute URL (/api/notify or https://…) or a VBService role name. The endpoint must return either an array or an object with a notifications array. Each entry:

Six recognized type values pick the row icon: update (package), alert (alert-triangle), watch (eye), system (settings), message (mail), stewardship (shield).

Polling

Set poll in milliseconds to re-fetch on an interval (e.g. poll="300000" for every 5 minutes). The component runs setInterval, cleared automatically when the element disconnects.

Toast on new

With toast-new set, every never-before-seen entry on a non-initial fetch fires a toast on the nearest <toast-msg>. The first fetch is silent so the page does not light up on every reload.

Watching tab

The panel grows two sub-tabs — All and Watching. The Watching tab reads VBStore namespace watches and lists every page the user has bookmarked via data-watch-page. Each row shows title, URL, time of last check, an “Changed” badge when the page’s content hash drifted since the user last visited, and a per-row Unwatch button. The tab’s count badge shows how many pages the user is watching.

The list refreshes whenever the panel opens, and also when a page-watch:add or page-watch:remove event fires anywhere in the document.

Attributes

AttributeModesDescription
modebothbanner or panel (default)
persistbannerVBStore key for dismiss state
variantbannerinfo, success, warning, error
positionbannertop (default) or bottom
expiresbannerDays until the banner re-shows after dismiss
srcpanelURL or VBService role for dynamic notifications
pollpanelPolling interval (ms)
toast-newpanelFire a toast on each new dynamic notification
storage-keypanelVBStore namespace for read-state (default notifications)

Events

EventDetailWhen
notification-wc:new{ notification }A previously-unseen entry arrived in a dynamic fetch
notification-wc:read{ id }An entry was marked read
notification-wc:dismiss{ id }Banner was dismissed
notification-wc:openPanel drawer opened
notification-wc:closePanel drawer closed

Accessibility

  • The bell trigger gets aria-haspopup="dialog" and tracks its open state with aria-expanded; the drawer carries role="dialog" aria-label="Notifications".
  • Outside-click and Esc both close the drawer; focus returns to the trigger on Esc.
  • Static <article> children remain in the light DOM — screen readers can navigate them by heading even when the panel is closed.
  • The unread count is announced when present (a numeric badge inside the trigger button).
  • Banner dismiss respects prefers-reduced-motion — no entry/exit animation.

Related

  • <toast-msg> — toast container used by toast-new
  • <consent-banner> — one-per-page consent dialog (different from a notification banner)
  • <settings-panel> — the trigger+panel pattern this component is modeled on
  • /go/ Convention — the namespace src="/go/notify" resolves to, and the lock-in problem it solves
  • Service Contracts — full JSON schema for the /go/notify endpoints this component reads