Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Dual-mode notification component: dismissible banner with persistent state, or bell-icon panel with per-item read tracking and optional dynamic feed.
<notification-wc> ships two modes that share an element name. Pick with mode="banner" or mode="panel" (the default).
VBStore under the namespace notifications; optional expires re-shows after N days.<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.
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.
<notification-wc mode="banner" persist="site-update-v3" variant="info" position="top"> <p> <strong>v3.0 is out.</strong> <a href="/changelog">See what's new</a> </p> <button type="button" value="dismiss">Dismiss</button></notification-wc>
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.
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.
<toast-msg position="top-end"></toast-msg> <notification-wc src="/api/notify" toast-new> <article data-id="release-3.0" data-type="update" data-date="2026-04-10"> <h3>v3.0 Released</h3> <p>New notification system.</p> <a href="/changelog#v3">Read more</a> </article> <article data-id="maintenance" data-type="alert" data-date="2026-04-12"> <h3>Scheduled Maintenance</h3> <p>Brief downtime Sunday 2am-4am UTC.</p> </article></notification-wc>
Read state is persisted under VBStore namespace notifications, key read-ids. Override the namespace per instance with storage-key="…".
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:
{ "id": "release-3.0", "type": "update", "title": "v3.0 Released", "body": "New notification system.", "url": "/changelog#v3", "date": "2026-04-10T00:00:00Z", "dismissed": false}
Six recognized type values pick the row icon: update (package), alert (alert-triangle), watch (eye), system (settings), message (mail), stewardship (shield).
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.
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.
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.
| Attribute | Modes | Description |
|---|---|---|
mode | both | banner or panel (default) |
persist | banner | VBStore key for dismiss state |
variant | banner | info, success, warning, error |
position | banner | top (default) or bottom |
expires | banner | Days until the banner re-shows after dismiss |
src | panel | URL or VBService role for dynamic notifications |
poll | panel | Polling interval (ms) |
toast-new | panel | Fire a toast on each new dynamic notification |
storage-key | panel | VBStore namespace for read-state (default notifications) |
| Event | Detail | When |
|---|---|---|
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:open | — | Panel drawer opened |
notification-wc:close | — | Panel drawer closed |
aria-haspopup="dialog" and tracks its open state with aria-expanded; the drawer carries role="dialog" aria-label="Notifications".<article> children remain in the light DOM — screen readers can navigate them by heading even when the panel is closed.prefers-reduced-motion — no entry/exit animation.<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 onsrc="/go/notify" resolves to, and the lock-in problem it solves/go/notify endpoints this component reads