TabStateSync is a lightweight TypeScript library for synchronizing state across multiple browser tabs.
It leverages the BroadcastChannel API (with a localStorage fallback) to keep your application's state or events in sync between tabs, with no backend required.
- Sync state between tabs in real time.
- Uses
BroadcastChannel(modern browsers) or falls back tolocalStorage(maximum compatibility). - Zero dependencies, minimal bundle size.
- Easy to use with React, Vue, or plain JS/TS.
npm install tabstatesyncSee also complete examples in examples/VanillaThemeExample.ts and examples/e2e-sync.html.
import { createTabStateSync } from 'tabstatesync';
// Create a channel
const tabSync = createTabStateSync('theme');
// Listen for changes from other tabs
tabSync.subscribe((newValue) => {
console.log('Theme changed in another tab:', newValue);
});
// Update value (all other tabs will be notified)
tabSync.set('dark');See also complete example in examples/ReactThemeExample.tsx.
import { useTabStateSync } from 'tabstatesync';
function ThemeSwitcher() {
const [theme, setTheme] = useTabStateSync('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Switch to {theme === 'light' ? 'dark' : 'light'}
</button>
);
}- Keep login/logout state synced across tabs
- Real-time shopping cart updates
- User preferences (e.g., theme) consistency
- Cross-tab notifications or dismissals
Creates a new sync channel for a given key with optional configuration.
Class for synchronizing state across tabs.
new TabStateSync<T>(key: string, options?: TabStateSyncOptions)
subscribe(callback: (value: T) => void): voidβ Registers a callback for value changes from other tabs.unsubscribe(callback: (value: T) => void): voidβ Removes a previously registered callback.set(value: T): voidβ Updates the value and notifies other tabs.destroy(): voidβ Cleans up listeners and disables the instance.
Configuration options for TabStateSync.
interface TabStateSyncOptions {
// Namespace prefix for localStorage keys to prevent collisions
namespace?: string; // default: 'tss'
// Enable simple encryption for localStorage storage
enableEncryption?: boolean; // default: false
// Secret key for encryption (use a random string)
encryptionKey?: string; // default: 'change-this-key'
// Enable debug logging of errors
debug?: boolean; // default: false
}useTabStateSync(key: string, initialValue: any, options?: TabStateSyncOptions): [any, (v: any) => void] (React only)
Custom React hook for syncing state across tabs.
- Does it sync between different devices or users?
- No. Sync only works between tabs/windows of the same browser and domain.
- Does it require a backend or cookies?
- No. It is 100% client-side and does not use cookies or any backend.
- What happens if BroadcastChannel is not available?
- It automatically falls back to using localStorage events for maximum compatibility. On Safari, due to browser limitations, the library uses a polling mechanism to detect changes, since the storage event is not reliably fired between tabs.
- Is it suitable for real-time multi-user collaboration?
- No. It is designed for client-side, same-user scenarios (e.g., SPAs, PWAs, admin panels).
- Does it work in incognito/private mode?
- Yes, as long as the browser supports BroadcastChannel or localStorage events in that mode. On Safari, the fallback uses polling to ensure sync even when the storage event does not fire.
- What about memory leaks?
- Always call
destroy()when you no longer need a TabStateSync instance (e.g., on component unmount).
- Always call
- Is my data secure when stored in localStorage?
- By default, data in localStorage is stored in plaintext. For improved security, enable the encryption option, but note that this is NOT suitable for highly sensitive data. The library uses a simple XOR encryption that helps prevent casual inspection but is not cryptographically secure.
- Data Security: The optional encryption feature provides basic protection against casual inspection of localStorage data. However, it is not a replacement for proper encryption and should not be used for highly sensitive information.
- XSS Protection: Always sanitize any HTML content before rendering it to the DOM, especially if it was received through TabStateSync.
- Error Handling: Enable debug mode during development to catch potential issues with data formatting or transport.
- Namespace Collisions: Use the namespace option to prevent key collisions with other applications or libraries using localStorage.
- Full support: Chrome, Edge, Firefox, Safari (latest)
- Falls back to
localStoragefor legacy browsers
Safari (desktop and iOS) does not reliably fire the storage event between tabs.
To ensure cross-tab sync, TabStateSync automatically enables a polling fallback only on Safari, checking for changes every 500ms. This ensures maximum compatibility, but may have a slight performance impact only on Safari. All other browsers use the more efficient storage event.
MIT
Contributions and suggestions are welcome!
TabStateSync is automatically tested in Chromium, Firefox, and WebKit (Safari) using Playwright. This ensures robust cross-browser compatibility, including fallback and edge cases.
- Build the JS bundle for browser tests:
npm run build:bundle
- Start a local server:
npm run serve:e2e
- In another terminal, run Playwright tests:
npm run test:e2e
- The tests will open browsers automatically and check sync between tabs, fallback to localStorage, and edge cases.
- To test the fallback, temporarily comment out or remove
window.BroadcastChannelin your browser's devtools or in the bundle, then rerun the E2E test. The library will use localStorage events for sync.
npm run build:bundleβ Build browser bundle for E2E/examplesnpm run serve:e2eβ Serve local files for browser testsnpm run test:e2eβ Run Playwright E2E tests