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.