Getting started
use-everywhere is useState, except the value exists in every tab,
window, and worker on your origin at once. It exists because the browser’s
cross-tab APIs are good but low-level, and there was no useState-shaped
way to use them — the full reasoning is in Why this exists. The
pitch fits in one sentence:
Let’s get a value living in every tab in about two minutes.
Install
Section titled “Install”pnpm add use-everywhere # React hooks (re-exports the full core)# or: npm i use-everywhere / yarn add use-everywhereIf you’re not using React, @use-everywhere/core is the same engine with no
framework attached — everything in these docs that isn’t a hook lives there.
Both packages ship ESM and CommonJS, so they resolve whether your toolchain
uses import or require — including Jest and other CJS setups, no
transformIgnorePatterns gymnastics required.
Next.js (App Router) and React Server Components
Section titled “Next.js (App Router) and React Server Components”The hooks are client-only — they use useSyncExternalStore and
BroadcastChannel, neither of which exists on the server. The package entry is
already marked 'use client', so importing the hooks never triggers a “you’re
importing a Server Component” error from inside the library. You still call them
from your own Client Component — the one file that reads shared state needs
'use client' at the top, same as any file using useState:
'use client';import { useSharedState } from 'use-everywhere';
export function Counter() { const [count, setCount] = useSharedState('count', 0); return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;}Render <Counter /> from any Server Component. Nothing renders shared state on
the server: on the server the hooks return your initial value via
getServerSnapshot, and the tabs converge the moment the client mounts.
Make a counter that doesn’t live in one tab
Section titled “Make a counter that doesn’t live in one tab”Swap useState for useSharedState and give the value a key:
import { useSharedState, usePeers } from 'use-everywhere';
function Counter() { // useState, but the value exists in every tab on this origin. const [count, setCount] = useSharedState('count', 0); const peers = usePeers();
return ( <button onClick={() => setCount((c) => c + 1)}> {count} — seen by {peers.length} other tabs </button> );}Same shape as useState: a value, a setter, updater functions work. There’s
no Provider to wrap and no store to configure — the key 'count' is the
identity, and any component in any tab that uses it shares the value. (Why no
Provider? A BroadcastChannel is already global to the origin; a React context
couldn’t scope it any further. More in
the mental model.)
Open a second tab
Section titled “Open a second tab”This is the whole payoff, so actually do it:
- Run your app and open it in two tabs, side by side.
- Click the button in either tab — both render the new count within a few milliseconds.
- Click both buttons as fast as you can. The counts never disagree: every write carries a version clock, and all tabs deterministically pick the same winner.
- Now open a third tab. It renders the current count from its very first
paint — not
0— because new tabs ask their peers for a snapshot before trusting the initial value.
That third step is the one that kills real bugs. A tab opened mid-payment
seeing 'processing' instead of 'idle' is exactly the difference between
one charge and two.
Which hook do I want?
Section titled “Which hook do I want?”| You are asking… | Reach for | Because |
|---|---|---|
| “What is the current value?” | useSharedState | Convergent, hydrates late joiners, survives tab churn |
| “What just happened?” | useMessage | Fire-and-forget events; no history, no cleanup |
| “Who else is here?” | usePeers | Live peer list via heartbeats |
| “How do I hear back from a window on another domain?” | useOpenedWindow | Validated 1:1 postMessage channel with a result |
When you’re torn between the first two, use this test:
Payment status: state. “Session renewed, restart your timers”: event.
See the whole thing running
Section titled “See the whole thing running”The repo’s demo app shows every feature on one screen — synced counter and
note, presence dots, a worker feeding data, and a real cross-origin payment
window (it opens 127.0.0.1 from localhost, which the browser treats as
genuinely different origins):
git clone https://github.com/rxova/use-everywhere && cd use-everywherepnpm install && pnpm build && pnpm devOpen http://localhost:5173 in two tabs and click around.
Where to next
Section titled “Where to next”- Why this exists — the bug that keeps coming back, and what we all hand-roll today instead of this library.
- The mental model — two ideas that make the whole API predictable. Read this one even if you skip the rest.
- Hooks — every hook in plain English, with all the options and examples.
- Guides — task-shaped walkthroughs: shared state, messages, payments, recipes, testing.
- Under the hood — version clocks, handshakes, and the security model, for when you want to reason about edge cases.