---
title: "Several documents — Vue"
description: "Open several documents at once: tabs, side-by-side panes, and which document each part of your UI talks to."
framework: "Vue"
source: "https://www.embedpdf.com/docs/headless/vue/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.vue`**

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

const documents = useDocuments();
const { documents: open, activeId } = useDocumentsState();
</script>

<template>
  <div role="tablist">
    <div v-for="document in open" :key="document.id">
      <button
        role="tab"
        :aria-selected="document.id === activeId"
        @click="documents.setActive(document.id)"
      >
        {{ document.name }}
      </button>
      <button :aria-label="`Close ${document.name}`" @click="documents.close(document.id)">×</button>
    </div>
  </div>
</template>
```

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.vue`**

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

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>

<template>
  <Viewer
    :engine="engine"
    :plugins="plugins"
    :initial-documents="[
      { source: ebook, name: 'Contract' },
      { source: ebook, name: 'Report' },
    ]"
  >
    <Tabs :ebook="ebook" />
    <DocumentGate>
      <template #fallback><p class="loading">Opening…</p></template>
      <TabTools />
      <Stage class="stage">
        <template #page><RenderLayer /></template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>
```

**`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.vue`**

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

defineProps<{ ebook: OpenSource }>();

const documents = useDocuments();
const { documents: open, activeId } = useDocumentsState();
</script>

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

**`TabTools.vue`**

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

const documents = useDocuments();
const { id, name } = useDocument();
const stage = useStage();
const zoomLevel = useStageState((state) => state.zoomLevel);

function rename(event: Event) {
  documents.rename(id.value, (event.target as HTMLInputElement).value);
}
</script>

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

## 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.vue`**

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

defineProps<{ documentId: string }>();
</script>

<template>
  <DocumentScope :id="documentId">
    <Stage>
      <template #page><RenderLayer /></template>
    </Stage>
    <ZoomControls />
  </DocumentScope>
</template>
```

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.vue`**

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

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>

<template>
  <Viewer
    :engine="engine"
    :plugins="plugins"
    :initial-documents="[
      { source: ebook, name: 'Original' },
      { source: ebook, name: 'Revised' },
    ]"
  >
    <SideBySide />
  </Viewer>
</template>
```

**`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.vue`**

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

const { name, pageCount } = useDocument();
const stage = useStage();
const zoomLevel = useStageState((state) => state.zoomLevel);
</script>

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

**`SideBySide.vue`**

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

const { documents } = useDocumentsState();
</script>

<template>
  <div class="split">
    <section v-for="document in documents" :key="document.id" class="pane">
      <DocumentScope :id="document.id">
        <DocumentGate>
          <template #fallback><p class="loading">Opening…</p></template>
          <Header />
          <Stage class="stage">
            <template #page><RenderLayer /></template>
          </Stage>
        </DocumentGate>
      </DocumentScope>
    </section>
  </div>
</template>
```

## 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.vue`**

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

const views = useViewManager();
const { panes, focusedPaneId } = useViewManagerState();
</script>

<template>
  <div class="panes">
    <section
      v-for="pane in panes"
      :key="pane.id"
      :data-focused="pane.id === focusedPaneId"
      @focusin="views.setFocusedPane(pane.id)"
    >
      <PaneTabs :pane="pane" />
      <DocumentScope v-if="pane.activeDocumentId" :id="pane.activeDocumentId">
        <Stage>
          <template #page><RenderLayer /></template>
        </Stage>
      </DocumentScope>
    </section>
  </div>
</template>
```

```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.vue`**

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

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>

<template>
  <Viewer
    :engine="engine"
    :plugins="plugins"
    :initial-documents="[
      { source: ebook, name: 'Contract' },
      { source: ebook, name: 'Report' },
    ]"
  >
    <Panes />
  </Viewer>
</template>
```

**`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.vue`**

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

const props = defineProps<{ pane: PaneInfo; canRemove: boolean }>();

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

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

<template>
  <section
    class="pane"
    :data-focused="pane.id === focusedPaneId"
    @pointerdown="views.setFocusedPane(pane.id)"
  >
    <div class="bar" role="tablist">
      <button
        v-for="id in pane.documentIds"
        :key="id"
        type="button"
        role="tab"
        class="tab"
        :aria-selected="id === pane.activeDocumentId"
        @click="views.setActiveDocument(pane.id, id)"
      >
        {{ nameOf(id) }}
      </button>
      <button
        type="button"
        class="button"
        :disabled="!pane.activeDocumentId || pane.documentIds.length < 2"
        @click="split"
      >
        Split
      </button>
      <button
        type="button"
        class="button"
        :disabled="!canRemove"
        @click="views.removePane(pane.id)"
      >
        Close pane
      </button>
    </div>
    <DocumentScope v-if="pane.activeDocumentId" :id="pane.activeDocumentId">
      <DocumentGate>
        <template #fallback><p class="empty">Opening…</p></template>
        <Stage class="stage">
          <template #page><RenderLayer /></template>
        </Stage>
      </DocumentGate>
    </DocumentScope>
    <p v-else class="empty">No document in this pane.</p>
  </section>
</template>
```

**`Panes.vue`**

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

const views = useViewManager();
const { panes } = useViewManagerState();

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

<template>
  <div class="panes">
    <Pane v-for="pane in panes" :key="pane.id" :pane="pane" :can-remove="panes.length > 1" />
  </div>
</template>
```

## 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 refs: your template updates when a value changes. 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 refs: your template updates when a value changes. 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.vue`**

```vue
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { useDocumentsEvent } from '@embedpdf/vue/runtime';

const router = useRouter();

useDocumentsEvent(
  (documents) => documents.onActiveChanged,
  ({ documentId }) => router.replace(`/documents/${documentId}`),
);
</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).
