The JS API & mute
The loader defines a small global, window.tapback. Most pages never need it — the bar
handles everything. Reach for it when your page's own UI should drive reactions: a custom
button, a keyboard shortcut, your own mute toggle, or a live counter rendered in your
design system. Paired with ambient mode, it replaces the bar entirely.
The surface
tapback.react(kind) // fire a reaction, e.g. tapback.react("heart")
tapback.mute() // silence falls + disable reactions; remembered per visitor
tapback.unmute() // and back
tapback.on("counts", cb) // cb(totals, entity) — now, and on every live update
tapback.ready() // Promise that resolves once the page has a session
react takes an optional trailing entity argument, and mute/unmute an optional
entity, for pages with more than one <tapback-bar>; leave it off on a normal
single-bar page. The counts callback
receives the entity as its second argument, so one listener serves a multi-bar page.
The global exists once the loader has run. Click handlers can reference it directly (a
click always lands after load); for code that runs at parse time next to an async
loader tag, wait for the element definition first:
await customElements.whenDefined("tapback-bar");
tapback.on("counts", …);
ready() resolves once the page has tried to open its session — after that, calls either
land or are dropped silently (a blocked or failed session never hangs your code).
There's a second global on the draft and preview templates that load
editor.js:
window.tapbackEditor.mount(target, opts) puts the reaction picker inside your CMS's own
UI and hands back the element to drive. It's an authoring surface, so it never reaches a
reader's page.
Fire a reaction from your own button
<button onclick="tapback.react('heart')">♥ love this</button>
An API reaction goes through the same path as a bar tap: the tap cooldown from your console config, the per-reader rate limits, and the same falling animation for everyone watching. Calls made before the page has a session are queued and sent once it does, so you can wire handlers without awaiting anything.
There are no errors to handle. An unknown kind, a muted visitor, a tap inside the
cooldown window or a rate-limited burst is dropped silently — the same way the bar treats
them. So does a bar carrying readonly —
it's display-only for a call as much as for a tap.
Render live counts in your own UI
tapback.on("counts", (totals) => {
document.querySelector("#hearts").textContent = totals.heart ?? 0;
});
The callback runs immediately with the current totals and again on every live update — including other readers' reactions arriving over the socket. It returns an unsubscribe function. Totals are the server's counts; your own tap shows up when the next update confirms it.
Mute
The bar ships a mute bell (readers can silence the falling animation; the choice is remembered). Two ways to drive it yourself:
The muted attribute mutes while it's present — a host-driven override that doesn't
touch the visitor's remembered choice. Suits a "quiet mode" your page already manages:
<tapback-bar entity="your-page-id" muted></tapback-bar>el.toggleAttribute("muted"); // from your own control
tapback.mute() / tapback.unmute() are the bell's own path: they persist, so the visitor stays
muted on their next visit. Use this when you're replacing the bell with your own switch
(set Mute control → hidden in the console to drop the built-in one). One nuance with
the bell hidden on a visible bar: the remembered mute is applied fresh each visit only
where an unmute path exists — keep your switch on every page that shows the bar, or use
the muted attribute and manage the state yourself.