---
title: "Several documents — Svelte"
description: "Open several documents at once: tabs, side-by-side panes, and which document each part of your UI talks to."
framework: "Svelte"
source: "https://www.embedpdf.com/docs/headless/svelte/documents/tabs"
---

# Several documents

A viewer can hold several documents at once, like tabs in a browser. One of them is the active
document, and every component talks to it unless you say otherwise:

**`tabs.svelte`**

```svelte
<script lang="ts">
  import { useDocuments, useDocumentsState } from '@embedpdf/svelte/runtime';

  const documents = useDocuments();
  const state = useDocumentsState();
</script>

<div role="tablist">
  {#each state.documents as document (document.id)}
    <div>
      <button
        role="tab"
        aria-selected={document.id === state.activeId}
        onclick={() => documents.setActive(document.id)}
      >
        {document.name}
      </button>
      <button aria-label="Close {document.name}" onclick={() => documents.close(document.id)}>×</button>
    </div>
  {/each}
</div>
```

Each document keeps its own state: its zoom and scroll position, its selection, its search and its
active tool. Switch tabs and back, and everything is where you left it.

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { localEngine } from '@embedpdf/engine';
  import TabTools from './TabTools.svelte';
  import Tabs from './Tabs.svelte';

  import '../tabs.css';

  const engine = localEngine();
  const plugins = [stagePlugin(), renderPlugin()];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer
  {engine}
  {plugins}
  initialDocuments={[
    { source: ebook, name: 'Contract' },
    { source: ebook, name: 'Report' },
  ]}
>
  <Tabs {ebook} />
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Opening…</p>
    {/snippet}
    <TabTools />
    <Stage class="stage">
      <RenderLayer />
    </Stage>
  </DocumentGate>
</Viewer>
```

**`tabs.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The tab strip: one tab per document, then the button that opens another */
.tabs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
}

.tab {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--surface);
  box-shadow: 0 1px 2px var(--shadow);
}

.tab[data-active='true'] {
  background: var(--tint);
  border-color: var(--focus);
}

.tab button {
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  background: none;
  border: 0;
  padding: 7px 10px;
  cursor: pointer;
}

.tab[data-active='true'] .name {
  color: var(--ink);
  font-weight: 600;
}

.tab .close {
  padding-left: 0;
  font-size: 15px;
  line-height: 1;
}

.tab button:focus-visible,
.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 9px;
}

/* What you do to the active tab */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 7px 12px;
  cursor: pointer;
  box-shadow: 0 1px 2px var(--shadow);
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.field {
  appearance: none;
  flex: 0 1 180px;
  min-width: 0;
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 7px 10px;
}

.field:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.readout {
  margin-left: auto;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 400px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`Tabs.svelte`**

```svelte
<script lang="ts">
  import { useDocuments, useDocumentsState, type OpenSource } from '@embedpdf/svelte/runtime';

  let { ebook }: { ebook: OpenSource } = $props();

  const documents = useDocuments();
  const tabs = useDocumentsState();
</script>

<div class="tabs" role="tablist">
  {#each tabs.documents as document (document.id)}
    <div class="tab" data-active={document.id === tabs.activeId}>
      <button
        type="button"
        role="tab"
        class="name"
        aria-selected={document.id === tabs.activeId}
        onclick={() => documents.setActive(document.id)}
      >
        {document.name}
      </button>
      <button
        type="button"
        class="close"
        aria-label="Close {document.name}"
        onclick={() => documents.close(document.id)}
      >
        ×
      </button>
    </div>
  {/each}
  <button
    type="button"
    class="button"
    aria-label="Open another copy"
    onclick={() => documents.open(ebook, { name: `Copy ${tabs.documents.length + 1}` })}
  >
    +
  </button>
</div>
```

**`TabTools.svelte`**

```svelte
<!-- The active tab: rename it, move it to the front, and zoom it. Each tab keeps its own zoom. -->
<script lang="ts">
  import { useDocument, useDocuments } from '@embedpdf/svelte/runtime';
  import { useStage, useStageState } from '@embedpdf/svelte/stage';

  const documents = useDocuments();
  const tab = useDocument();
  const stage = useStage();
  const zoomLevel = useStageState((state) => state.zoomLevel);
</script>

<div class="toolbar">
  <input
    class="field"
    aria-label="Tab name"
    value={tab.name ?? ''}
    oninput={(event) => documents.rename(tab.id, event.currentTarget.value)}
  />
  <button type="button" class="button" onclick={() => documents.move(tab.id, 0)}>
    Move to front
  </button>
  <button type="button" class="button" aria-label="Zoom out" onclick={() => stage.zoomOut()}>
    −
  </button>
  <button type="button" class="button" aria-label="Zoom in" onclick={() => stage.zoomIn()}>
    +
  </button>
  <output class="readout">{Math.round(zoomLevel.current * 100)}%</output>
</div>
```

## Manage the tabs

```ts
documents.setActive(id);
documents.rename(id, 'Signed contract');
documents.move(id, 0); // to the front
await documents.close(id);
```

Each entry in `documents`, from `useDocumentsState()`, has the document's `id`, its `name`, and its
`status`: `'loading'`, `'locked'`, `'ready'` or `'error'`, so a tab can show a spinner or a lock.
Every field is listed under [State](#state).

## Talk to a specific document

A component talks to the active document. Inside `<DocumentScope>`, it talks to the one you name,
whatever is active:

**`document-scope.svelte`**

```svelte
<script lang="ts">
  import { DocumentScope } from '@embedpdf/svelte/runtime';
  import { RenderLayer } from '@embedpdf/svelte/render';
  import { Stage } from '@embedpdf/svelte/stage';
  import ZoomControls from './ZoomControls.svelte';

  let { documentId }: { documentId: string } = $props();
</script>

<DocumentScope id={documentId}>
  <Stage>
    <RenderLayer />
  </Stage>
  <ZoomControls />
</DocumentScope>
```

Everything inside, such as the Stage, `useSearch()` and `useSelection()`, uses that document.
That's how you show two documents at once, or a preview of one while another is active.

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { renderPlugin } from '@embedpdf/svelte/render';
  import { stagePlugin } from '@embedpdf/svelte/stage';
  import { localEngine } from '@embedpdf/engine';
  import SideBySide from './SideBySide.svelte';

  import '../scope.css';

  const engine = localEngine();
  const plugins = [stagePlugin(), renderPlugin()];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer
  {engine}
  {plugins}
  initialDocuments={[
    { source: ebook, name: 'Original' },
    { source: ebook, name: 'Revised' },
  ]}
>
  <SideBySide />
</Viewer>
```

**`scope.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* Two documents: stacked, and side by side where there's room */
.split {
  container-type: inline-size;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.pane {
  flex: 1 1 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

@container (min-width: 620px) {
  .pane {
    flex: 1 1 0;
  }
}

/* A pane's header: its document, and its own zoom */
.header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--muted);
}

.header strong {
  margin-right: auto;
  color: var(--ink);
  font-weight: 600;
}

.button {
  min-width: 34px;
  font: inherit;
  font-size: 14px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 7px 8px;
  cursor: pointer;
  box-shadow: 0 1px 2px var(--shadow);
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.readout {
  min-width: 44px;
  text-align: center;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 340px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`Header.svelte`**

```svelte
<!-- The same component in both panes: each talks to the document its <DocumentScope> names. -->
<script lang="ts">
  import { useDocument } from '@embedpdf/svelte/runtime';
  import { useStage, useStageState } from '@embedpdf/svelte/stage';

  const document = useDocument();
  const stage = useStage();
  const zoomLevel = useStageState((state) => state.zoomLevel);
</script>

<div class="header">
  <strong>{document.name}</strong>
  {document.pageCount} pages
  <button type="button" class="button" aria-label="Zoom out" onclick={() => stage.zoomOut()}>
    −
  </button>
  <output class="readout">{Math.round(zoomLevel.current * 100)}%</output>
  <button type="button" class="button" aria-label="Zoom in" onclick={() => stage.zoomIn()}>
    +
  </button>
</div>
```

**`SideBySide.svelte`**

```svelte
<script lang="ts">
  import { DocumentGate, DocumentScope, useDocumentsState } from '@embedpdf/svelte/runtime';
  import { RenderLayer } from '@embedpdf/svelte/render';
  import { Stage } from '@embedpdf/svelte/stage';
  import Header from './Header.svelte';

  const open = useDocumentsState();
</script>

<div class="split">
  {#each open.documents as document (document.id)}
    <section class="pane">
      <DocumentScope id={document.id}>
        <DocumentGate>
          {#snippet fallback()}
            <p class="loading">Opening…</p>
          {/snippet}
          <Header />
          <Stage class="stage">
            <RenderLayer />
          </Stage>
        </DocumentGate>
      </DocumentScope>
    </section>
  {/each}
</div>
```

## Side by side

The view manager arranges documents into panes, each with its own tabs, like split editors in a
code editor. Register `viewManagerPlugin()`, then render one Stage per pane:

**`panes.svelte`**

```svelte
<script lang="ts">
  import { DocumentScope } from '@embedpdf/svelte/runtime';
  import { RenderLayer } from '@embedpdf/svelte/render';
  import { Stage } from '@embedpdf/svelte/stage';
  import { useViewManager, useViewManagerState } from '@embedpdf/svelte/view-manager';
  import PaneTabs from './PaneTabs.svelte';

  const views = useViewManager();
  const state = useViewManagerState();
</script>

<div class="panes">
  {#each state.panes as pane (pane.id)}
    <section data-focused={pane.id === state.focusedPaneId} onfocusin={() => views.setFocusedPane(pane.id)}>
      <PaneTabs {pane} />
      {#if pane.activeDocumentId}
        <DocumentScope id={pane.activeDocumentId}>
          <Stage>
            <RenderLayer />
          </Stage>
        </DocumentScope>
      {/if}
    </section>
  {/each}
</div>
```

```ts
views.splitPane(documentId); // the document in a new pane, beside the focused one
views.setActiveDocument(paneId, documentId); // show another of the pane's tabs
views.moveDocumentBetween(fromPaneId, toPaneId, documentId); // drag a tab to another pane
views.removePane(paneId); // its documents move to another pane
```

A document opened with `documents.open()` lands in the focused pane. Every call is listed under
[Methods](#methods).

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { renderPlugin } from '@embedpdf/svelte/render';
  import { stagePlugin } from '@embedpdf/svelte/stage';
  import { viewManagerPlugin } from '@embedpdf/svelte/view-manager';
  import { localEngine } from '@embedpdf/engine';
  import Panes from './Panes.svelte';

  import '../panes.css';

  const engine = localEngine();
  const plugins = [stagePlugin(), renderPlugin(), viewManagerPlugin()];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer
  {engine}
  {plugins}
  initialDocuments={[
    { source: ebook, name: 'Contract' },
    { source: ebook, name: 'Report' },
  ]}
>
  <Panes />
</Viewer>
```

**`panes.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The panes: stacked, and side by side where there's room */
.panes {
  container-type: inline-size;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.pane {
  flex: 1 1 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surface);
}

/* The focused pane is where documents you open land */
.pane[data-focused='true'] {
  border-color: var(--focus);
}

@container (min-width: 620px) {
  .pane {
    flex: 1 1 0;
  }
}

/* A pane's tabs, then what you do to the pane */
.bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.tab {
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  background: none;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 6px 10px;
  cursor: pointer;
}

.tab[aria-selected='true'] {
  color: var(--ink);
  font-weight: 600;
  background: var(--tint);
  border-color: var(--line-strong);
}

.button {
  font: inherit;
  font-size: 12px;
  font-weight: 550;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 6px 10px;
  cursor: pointer;
}

.button:first-of-type {
  margin-left: auto;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.tab:focus-visible,
.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* The pages */
.stage {
  height: 320px;
  border-radius: 10px;
  background: var(--canvas);
}

.empty {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`Pane.svelte`**

```svelte
<script lang="ts">
  import { DocumentGate, DocumentScope, useDocumentsState } from '@embedpdf/svelte/runtime';
  import { RenderLayer } from '@embedpdf/svelte/render';
  import { Stage } from '@embedpdf/svelte/stage';
  import {
    useViewManager,
    useViewManagerState,
    type PaneInfo,
  } from '@embedpdf/svelte/view-manager';

  let { pane, canRemove }: { pane: PaneInfo; canRemove: boolean } = $props();

  const views = useViewManager();
  const focusedPaneId = useViewManagerState((state) => state.focusedPaneId);
  const open = useDocumentsState();
  const nameOf = (id: string) => open.documents.find((document) => document.id === id)?.name ?? id;

  function split() {
    if (pane.activeDocumentId) views.splitPane(pane.activeDocumentId, { from: pane.id });
  }
</script>

<section
  class="pane"
  aria-label="Document pane"
  data-focused={pane.id === focusedPaneId.current}
  onpointerdown={() => views.setFocusedPane(pane.id)}
>
  <div class="bar" role="tablist">
    {#each pane.documentIds as id (id)}
      <button
        type="button"
        role="tab"
        class="tab"
        aria-selected={id === pane.activeDocumentId}
        onclick={() => views.setActiveDocument(pane.id, id)}
      >
        {nameOf(id)}
      </button>
    {/each}
    <button
      type="button"
      class="button"
      disabled={!pane.activeDocumentId || pane.documentIds.length < 2}
      onclick={split}
    >
      Split
    </button>
    <button
      type="button"
      class="button"
      disabled={!canRemove}
      onclick={() => views.removePane(pane.id)}
    >
      Close pane
    </button>
  </div>
  {#if pane.activeDocumentId}
    <DocumentScope id={pane.activeDocumentId}>
      <DocumentGate>
        {#snippet fallback()}
          <p class="empty">Opening…</p>
        {/snippet}
        <Stage class="stage">
          <RenderLayer />
        </Stage>
      </DocumentGate>
    </DocumentScope>
  {:else}
    <p class="empty">No document in this pane.</p>
  {/if}
</section>
```

**`Panes.svelte`**

```svelte
<script lang="ts">
  import { useViewManager, useViewManagerState } from '@embedpdf/svelte/view-manager';
  import Pane from './Pane.svelte';

  const views = useViewManager();
  const layout = useViewManagerState();

  // On load, both documents land in the first pane: put the second one beside it, once.
  let splitOnLoad = true;
  $effect(() => {
    const panes = layout.panes;
    if (splitOnLoad && panes.length === 1 && panes[0].documentIds.length === 2) {
      splitOnLoad = false;
      views.splitPane(panes[0].documentIds[1]);
    }
  });
</script>

<div class="panes">
  {#each layout.panes as pane (pane.id)}
    <Pane {pane} canRemove={layout.panes.length > 1} />
  {/each}
</div>
```

## Methods

What `useDocuments()` gives you for several documents. Opening and closing are on
[Opening documents](opening#methods), downloading and printing on
[Saving & downloading](saving#methods).

| Method              | What it does                                                                                                                     |
| :------------------ | :------------------------------------------------------------------------------------------------------------------------------- |
| `setActive(id)`     | Makes a document the active one: the one every component talks to outside `<DocumentScope>`. [Manage the tabs](#manage-the-tabs) |
| `rename(id, name)`  | Changes the name its tab shows. The file doesn't change.                                                                         |
| `move(id, index)`   | Moves a document to another place in the tab order                                                                               |
| `swap(id, otherId)` | Swaps two documents in the tab order                                                                                             |
| `setOrder(ids)`     | Puts every document in a new order at once. `ids` holds the id of every open document.                                           |

Everything `useViewManager()` gives you.

| Method                                                          | What it does                                                                                                                                              |
| :-------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `splitPane(documentId, { from? })`                              | Moves an open document into a new pane beside the focused one, or beside `from`, and focuses it. Returns the new pane's id. [Side by side](#side-by-side) |
| `createPane({ documentIds? })`                                  | Adds a pane, empty or with these documents moved into it, and focuses it. Returns its id.                                                                 |
| `removePane(paneId, { moveDocumentsTo? })`                      | Closes a pane. Its documents move to `moveDocumentsTo`, or else to the focused or the last pane.                                                          |
| `movePane(paneId, index)`                                       | Moves a pane to another place in the order, for reordering panes by dragging                                                                              |
| `setFocusedPane(paneId)`                                        | Makes a pane the focused one, where new documents open. `null` focuses none.                                                                              |
| `setActiveDocument(paneId, documentId)`                         | Shows another of the pane's documents                                                                                                                     |
| `addDocument(paneId, documentId, index?)`                       | Adds a document to a pane's tabs. A document is in one pane at a time, so it leaves the pane it was in.                                                   |
| `removeDocument(paneId, documentId)`                            | Takes a document out of a pane, without closing it                                                                                                        |
| `moveDocumentWithin(paneId, documentId, index)`                 | Moves a tab to another place inside its pane                                                                                                              |
| `moveDocumentBetween(fromPaneId, toPaneId, documentId, index?)` | Moves a tab from one pane to another, for dragging tabs between panes                                                                                     |

## State

Everything `useDocumentsState()` gives you, as one reactive object: read its fields, and your markup updates when they change. The method in the last column reads the same value once.

| Field       | What it is                                                                                                                                            | Read once                                        |
| :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------- |
| `documents` | Every open document, in tab order, including ones that are loading, locked or failed. Each has the fields [`useDocument()`](opening#state) gives you. | `list()`, `has(id)`, `getCount()`, `getOrder()`  |
| `activeId`  | The active document's id, or `null` with no document open                                                                                             | `getActiveId()`, or `getActive()` for its fields |

`has(id)` says whether a document is open, `getCount()` how many are, and `getOrder()` gives just their
ids. One document's fields come from `get(id)`, on [Opening documents](opening#state).

Everything `useViewManagerState()` gives you, as one reactive object: read its fields, and your markup updates when they change. The method in the last column reads the same value once.

| Field           | What it is                                                                                                           | Read once                                                                           |
| :-------------- | :------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| `panes`         | Every pane, in order. Each has its `id`, its `documentIds` (its tabs, in order) and the `activeDocumentId` it shows. | `listPanes()`, `getPane(paneId)`, `getPaneOrder()`, `getPaneOfDocument(documentId)` |
| `focusedPaneId` | The pane new documents open in, or `null`                                                                            | `getFocusedPaneId()`                                                                |

`getPane(paneId)` reads one pane, `getPaneOrder()` gives just their ids, and
`getPaneOfDocument(documentId)` says which pane holds a document.

## Events

**`active-route.svelte`**

```svelte
<script lang="ts">
  import { goto } from '$app/navigation';
  import { useDocumentsEvent } from '@embedpdf/svelte/runtime';

  useDocumentsEvent(
    (documents) => documents.onActiveChanged,
    ({ documentId }) => goto(`/documents/${documentId}`, { replaceState: true }),
  );
</script>
```

| Event                          | Fires when                             | Carries                                |
| :----------------------------- | :------------------------------------- | :------------------------------------- |
| `onActiveChanged`              | Another document became the active one | `documentId`, `previousDocumentId`     |
| View manager `onPaneCreated`   | A pane was added                       | `paneId`                               |
| View manager `onPaneRemoved`   | A pane was closed                      | `paneId`                               |
| View manager `onFocusChanged`  | Another pane got the focus             | `paneId`, or `null` when none has it   |
| View manager `onDocumentMoved` | A document moved between panes         | `documentId`, `fromPaneId`, `toPaneId` |

The view manager's events come from `useViewManagerEvent()`. Opening and closing documents are on
[Opening documents](opening#events).

## Good to know

### Memory

Each open document holds its file in the engine's memory. Close documents the user is done with,
especially large ones.

### One Stage per view

A Stage shows one document. For two documents at once, render two Stages, each in its own
`<DocumentScope>`. For two views of one document, see
[Two views of one document](../viewing/stage#two-views-of-one-document).
