---
title: "Blok Tab Sync — One Document Across Browser Tabs"
description: "Keep a Blok document live across the tabs of one browser with documentId: what syncs, what never does, and the limits."
source: https://blokeditor.com/docs/tab-sync/
lastmod: 2026-10-06
---

Framework JavaScript

Getting started Tab sync

# Tab sync: one document in every tab

Keep one document live across the tabs of one browser, with no server.

Last updated Oct 6, 2026 [Edit this page on GitHub](https://github.com/JackUait/blok/blob/main/docs/src/components/api/TabSyncContent.tsx)

## What it does

Open the same document in two tabs of one browser. A change in one tab shows up in the others. No server is involved.

Tab sync is on by default. `tabSync: false` turns it off. `tabSync: { settings: false }` keeps the document in sync but stops sharing locale, theme mode and width.

With `collaboration` set, tab sync stays off. The collaboration server already keeps every tab in sync.

## Where the document id comes from

Tabs find each other by a document id. Pass it as `documentId`. Use the id your app already loads and saves the document by:

- A route param, such as `/notes/:id`.
- The id inside your `persistence` URLs.
- The key of the database record.
- A fixed name, such as `'notes'`, when the site has a single editor.
- A new unique id for a new document.

For a new id, `crypto.randomUUID()` works on HTTPS and on localhost. It is missing on a plain-http page, because it needs a secure context. Any unique string works just as well.

The id must be unique across the whole site. If two of your customers can share an id, add a prefix, such as `'acme:42'`. An empty string is refused, and `isReady` rejects.

Without `documentId`, Blok uses the `id` it writes into saved data, plus the page path. That covers a document that `persistence` loads. A document with no stored id joins after its first `persistence` save.

A document passed as `data` with its own `id` never joins on its own. Neither does one saved only through `onSave`, or an empty one. Pass `documentId` in those cases.

## What syncs

These follow across the tabs that show the same document:

- The document content.
- The content width.
- Whether each toggle is open. This is personal to the browser and never saved in the document.

Locale and theme mode follow across every tab of the same origin that runs Blok. Recent colors, links and code languages, the emoji skin tone, and media volume, speed and loop are stored by the browser. They are shared whatever `tabSync` says.

These never sync:

- Selection and caret.
- Scroll position.
- Open menus and popovers.
- Find in page.
- Read-only mode.
- Theme tokens and other host config.
- Media playback position.

## The tab you work in saves

Only one tab saves at a time: the tab you are working in. It calls `persistence.save` and `onSave`, including the save handlers inside the framework adapters. Tabs in the background are passive.

When you switch to another tab, that tab takes over, usually within half a second. The tab you left first saves what it still holds. If a save is still running, the new tab waits for it, which can take a few seconds.

So a binding that rides `onSave` stays current in the tab you use:

- Vue `v-model:data`.
- Angular `[formControl]`.
- A React `onSave` that feeds your state.

In a background tab such a binding can lag. To read the document there, use `editor.save()`.

A read-only tab never saves. When the saving tab closes, another open tab takes over.

`onChange` still fires in every tab. Its events carry `origin`, which is `'tab'` for a change made in another tab.

## Toggles

Whether a toggle is open is personal. Each browser remembers its own state, and it is never saved in the document. The same holds for toggleable headings.

- A toggle starts collapsed.
- A toggle you create starts open for you in this browser.
- With tab sync on, opening a toggle in one tab opens it in the other tabs of this browser that show the same document.

The state is keyed by `documentId`, the `collaboration` doc, or else the document's `id`. If your app drops `id` when it stores the document, the state is lost on reload. Copies that share an id share the state too.

`isOpen` is no longer saved or read. A document saved with `isOpen` opens with its toggles collapsed. The state lives in `api.viewState` under the key `'open'`.

## Limits

The document stays separate in each tab in these cases:

- Inside a third-party iframe in Chrome and Firefox. Those browsers split tab channels by the top-level site.
- On a plain-http page. Web Locks need a secure context, and localhost counts as one.
- Across Blok versions that use different tab sync protocols.
- In a tab where the user typed before it joined. It stays separate until reload.

A separate tab saves on its own, next to the tab that saves. It never takes over saving, even while you work in it. Only a `persistence` whose `load` returns a version can catch that clash.

A tab opened while the saving tab is frozen in the background works on its own. It joins when that tab wakes, unless the user typed in it meanwhile.

If the saving tab is frozen when you switch to another tab, the tab you switch to takes over after about 4 seconds.

Typing reaches the other tabs within about 400 ms.

If your app copies stored documents and opens every document at one path, the copies share an id. Pass `documentId`, or remove `id` when you copy.

`documentId` and `tabSync` are fixed for the editor's life. To switch documents, recreate the editor. In React, change its `key` or the `useBlok` deps. In Vue and Angular, change `recreateKey`.
