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(true) // silence falls + disable reactions; remembered per visitor
tapback.mute(false) // unmute
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 and mute take an optional trailing entity argument 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).
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.
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(true/false) is the bell's own path: it persists, 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.