Install the widget

One script tag, under 5 KB, no cookies — the whole integration.

The Bellnote widget is the bell-and-unread-badge pattern your users already know, without the weight. The entire integration is one script tag.

The snippet

<script src="https://cdn.bellnote.dev/widget.js" data-project="PROJ_ID" async></script>

Replace PROJ_ID with your project ID (shown on the publish-success screen and in project settings). Paste the tag before </body> on every page where the bell should appear. No npm package, no build step, no framework plugin.

What your users get

  • A launcher button (bell icon) pinned to the corner you choose — fixed position, no layout shift.
  • An unread badge counting entries published since the visitor last opened the panel.
  • A panel with your latest entries on first click — the panel chunk lazy-loads only when needed, cached by the CDN.
  • Subscribe by email inline in the panel (double opt-in), plus emoji reactions per entry.

Performance and privacy

  • Core loader is under 5 KB gzipped — enforced in CI, the build fails if it grows.
  • First-paint network cost is the loader plus one config request, in parallel.
  • No cookies, no fingerprinting. Read state lives in localStorage under bellnote:<projectId>:lastSeen. Privacy is a feature.
  • Zero render-blocking: the script is async and everything else loads lazily.

Theming (every plan, including Free)

Set these in Widget customization in the portal; they're served to the widget via the config endpoint:

Setting What it controls
primaryColor Launcher and accent color
textColor Panel text
background Panel background
position bottom-right or bottom-left
launcherLabel Accessible label, e.g. "Updates"
customCss Allowlisted properties only: color, background, font, border-radius, size tokens. url(), expression(), and @import are stripped.

Free plans show a small "Powered by Bellnote" link; paid plans can turn it off.

SPA and framework notes

The widget is framework-agnostic vanilla JS. It survives client-side route changes, cleans up if the script is removed, and exposes a single global, window.Bellnote, with open() and close(). It works identically on React, Vue, Svelte, Rails, or plain HTML.

Accessibility

The launcher is a real <button> with an aria-label. The panel is focus-trapped, Esc closes it, and animations respect prefers-reduced-motion.