Cross-tab communication for the modern web — shared state, presence, leader election, locks, duplicate detection, and request/response between every open tab of your app.
Every tab posts a heartbeat ~every 1.5s. Stale tabs are evicted after 5s. The leader is the oldest live tab — re-elected the moment it leaves.
Update the counter or theme — every open tab updates in real time. New tabs ask peers for the current state on boot.
Broadcast to every tab, send a private message to one tab, or run a request/response round-trip.
A held lock is broadcast to every tab. Click "Acquire" in two tabs simultaneously — the second one blocks until the first releases.
Every cross-tab event in this tab — open, close, focus, leader changes, messages, state, duplicate, locks.
BroadcastChannel where supported, localStorage as a fallback, and a clean typed API on top — so your app behaves predictably no matter how users open it.
Every tab announces itself and posts a heartbeat. Stale tabs are evicted automatically. Subscribe to open, close, focus and blur events.
tabs.on('open', (e) => console.log('+ tab', e.tab.id));
The oldest live tab is the leader. When it leaves, a new leader is elected and broadcast — perfect for "only one tab runs the poller".
if (tabs.isLeader) startPolling(); tabs.on('leader', (e) => {/* take over */});
One typed state object, synchronized across every open tab. Persisted to localStorage so new tabs hydrate instantly.
tabs.setState({ theme: 'dark' }); tabs.on('state', (e) => render(e.state));
Fire-and-forget broadcasts to every tab, or send a typed message to a single peer by id.
tabs.broadcast('logout'); tabs.send(peerId, 'focus-input');
Promise-based RPC between tabs. Register a handler on one side, await the result on the other.
tabs.handle('double', ({x}) => x*2); await tabs.request(id, 'double', {x:21});
A mutex that survives across tabs. Held locks heartbeat so abandoned locks auto-release after a configurable timeout.
await tabs.lock('critical', async () => { /* exclusive section */ });
Detect when a user opens "Duplicate tab" — the new tab inherits the same sessionStorage lineage and is flagged instantly.
if (tabs.isDuplicate) showWarning(); tabs.on('duplicate', alertUser);
One call to refuse a second tab — redirect, close, or show a "switch to other tab" overlay.
tabs.singleton((others) => { location.href = '/already-open'; });
~3 KB gzipped, zero dependencies, ESM + UMD + types. Works with React, Vue, Svelte, vanilla — any framework, any year.
import { getTabs } from '@buildwithdarsh/tabjs';
$ npm install @buildwithdarsh/tabjs