---
title: "Toolbar — Vue"
description: "A toolbar that fits itself to the space it has, shrinking buttons and moving them into a menu instead of wrapping, with every pixel yours."
framework: "Vue"
source: "https://www.embedpdf.com/docs/headless/vue/ui/toolbar"
---

# Toolbar

A viewer's toolbar has to fit a phone and a wide screen. `<Toolbar>` takes a list of what should be in
it, measures what fits, and makes room when it has to: labels become icons, a group folds into one
button, and what's left over goes into a "More" menu. You draw every button.

The items are [commands](commands), so each one already knows its label, icon, whether it's enabled,
and what it does. These are from the [standard commands](commands#the-standard-commands):

**`toolbar.vue`**

```vue
<script setup lang="ts">
import { Toolbar, group, item } from '@embedpdf/vue/toolbar';
import Icon from './Icon.vue';

const bar = {
  id: 'main',
  sections: {
    start: [group('navigation', ['page:previous', 'page:next'])],
    center: [group('zoom', ['zoom:out', item('zoom:in', { variants: ['icon+label', 'icon'] })])],
    end: [group('tools', ['tool:pointer', 'tool:pan', 'tool:highlight'], { collapse: 'menu' })],
  },
};
</script>

<template>
  <Toolbar :bar>
    <template #command="{ command, variant, run }">
      <button
        :disabled="!command.enabled"
        :aria-pressed="command.active"
        :title="command.label"
        @click="run()"
      >
        <Icon :name="command.icon" />
        <template v-if="variant === 'icon+label'">{{ command.label }}</template>
      </button>
    </template>
  </Toolbar>
</template>
```

**`App.vue`**

```vue
<script setup lang="ts">
import { ref } from 'vue';
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 { interactionPlugin } from '@embedpdf/vue/interaction';
import { SelectionLayer, selectionPlugin } from '@embedpdf/vue/selection';
import { AnnotationLayer, annotationPlugin } from '@embedpdf/vue/annotation';
import { commandsPlugin, standardCommands } from '@embedpdf/vue/commands';
import { localEngine } from '@embedpdf/engine';
import MainToolbar from './MainToolbar.vue';

import '../basic.css';

const engine = localEngine();
const plugins = [
  stagePlugin(),
  renderPlugin(),
  interactionPlugin(),
  selectionPlugin(),
  annotationPlugin(),
  commandsPlugin({ commands: standardCommands }),
];

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

// Narrow the toolbar to watch it make room: labels go first, then the tools fold, then "More".
const width = ref(100);
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <label class="width">
        Toolbar width
        <input v-model.number="width" type="range" min="30" max="100" />
        <output>{{ width }}%</output>
      </label>
      <div class="frame" :style="{ width: `${width}%` }">
        <MainToolbar />
      </div>
      <Stage class="stage">
        <template #page>
          <RenderLayer />
          <SelectionLayer />
          <AnnotationLayer />
        </template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>
```

**`basic.css`**

```css
/* Colors, and the toolbar's own parts (its folded groups and "More" menu) to match */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);

  --epdf-toolbar-surface: var(--surface);
  --epdf-toolbar-border: var(--line);
  --epdf-toolbar-active: var(--tint);
}

/* The width slider */
.width {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  font-size: 13px;
  color: var(--muted);
}

.width input {
  flex: 1;
  max-width: 220px;
  accent-color: var(--accent);
}

.width output {
  min-width: 40px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* The toolbar, above the pages */
.frame {
  margin-bottom: 14px;
  padding: 6px;
  box-sizing: border-box;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 1px 2px var(--shadow);
  color: var(--ink);
  font-size: 13px;
}

.toolbar {
  min-height: 34px;
}

.button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  min-width: 32px;
  justify-content: center;
  font: inherit;
  font-weight: 550;
  white-space: nowrap;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 0 8px;
  cursor: pointer;
}

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

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

/* The active tool shows as pressed */
.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

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

.icon {
  font-size: 16px;
  line-height: 1;
}

/* The pages */
.stage {
  height: 420px;
  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);
}
```

**`MainToolbar.vue`**

```vue
<script setup lang="ts">
import { Toolbar, group, item } from '@embedpdf/vue/toolbar';
import type { BarSchema } from '@embedpdf/vue/toolbar';

// Your icons, by the names the standard commands give them.
const ICONS: Record<string, string> = {
  'previous-page': '‹',
  'next-page': '›',
  'zoom-out': '−',
  'zoom-in': '+',
  'fit-width': '↔',
  pointer: '↖',
  pan: '✥',
  highlight: '▍',
  underline: 'U̲',
  ink: '〰',
  download: '↓',
  print: '⎙',
};

const bar: BarSchema = {
  id: 'main',
  sections: {
    start: [group('pages', ['page:previous', 'page:next'])],
    center: [
      group('zoom', [
        'zoom:out',
        item('zoom:in', { variants: ['icon+label', 'icon'] }),
        item('zoom:fit-width', { variants: ['icon+label', 'icon'], importance: 2 }),
      ]),
    ],
    end: [
      group('tools', ['tool:pointer', 'tool:pan', 'tool:highlight', 'tool:underline', 'tool:ink'], {
        collapse: 'menu',
      }),
      group('document', [item('document:download', { importance: 5 }), 'document:print']),
    ],
  },
};
</script>

<template>
  <Toolbar :bar="bar" class="toolbar">
    <template #command="{ command, variant, run }">
      <button
        type="button"
        class="button"
        :title="command.label"
        :aria-label="command.label"
        :disabled="!command.enabled"
        :aria-pressed="command.active"
        @click="run()"
      >
        <span class="icon" aria-hidden="true">
          {{ ICONS[command.icon ?? ''] ?? command.label.charAt(0) }}
        </span>
        <span v-if="variant === 'icon+label'">{{ command.label }}</span>
      </button>
    </template>
  </Toolbar>
</template>
```

Resize the window, and the toolbar adjusts on its own. It measures real rendered widths, so a longer
translation or a bigger font just works.

## How it makes room

When the items don't fit, the toolbar takes these steps, in order, until they do:

1. **An item gets smaller**: from its first `variant` to the next, such as `'icon+label'` to `'icon'`.
2. **A group folds** into one button with a menu, when it has `collapse: 'menu'`, or into a dropdown
   with `collapse: 'select'`.
3. **An item moves into the "More" menu**, the least important first.

`importance` decides the order, from `1` (goes first) to `5` (never leaves the bar):

```ts
item('document:download', { importance: 5 }); // always visible
group('annotate', ['tool:note', 'tool:ink'], { importance: 2 });
```

## Sections

`start`, `center` and `end` are the toolbar's left, middle and right. The center stays centered in the
space the other two leave. In a right-to-left language, `start` and `end` swap sides.

`orientation="vertical"` stands the toolbar on its side, for a rail beside the pages: `start`,
`center` and `end` become its top, middle and bottom, and it fits the height you give it, so give it
one. Every command draws at `'icon'`, so put its label in a tooltip. Groups fold and items move into
"More" the same way, and the default menus open beside it.

**`toolbar-vertical.vue`**

```vue
<script setup lang="ts">
import { Toolbar, group } from '@embedpdf/vue/toolbar';
import Icon from './Icon.vue';

const rail = {
  id: 'rail',
  sections: {
    start: [group('tools', ['tool:pointer', 'tool:pan', 'tool:highlight'])],
    end: [group('zoom', ['zoom:in', 'zoom:out'])],
  },
};
</script>

<template>
  <Toolbar :bar="rail" orientation="vertical" aria-label="Tools" style="height: 100%">
    <template #command="{ command, run }">
      <button
        :disabled="!command.enabled"
        :aria-pressed="command.active"
        :title="command.label"
        @click="run()"
      >
        <Icon :name="command.icon" />
      </button>
    </template>
  </Toolbar>
</template>
```

## Your own items

Not everything is a command: a page number input, a search box. Put a custom item in the bar and draw
it:

**`toolbar-custom.vue`**

```vue
<script setup lang="ts">
import { Toolbar, custom, group } from '@embedpdf/vue/toolbar';
import ToolbarButton from './ToolbarButton.vue';
import PageNumberInput from './PageNumberInput.vue';

const bar = {
  id: 'main',
  sections: {
    center: [group('page', [custom('page-number', 'page:go-to', { variants: ['full', 'compact'] })])],
  },
};
</script>

<template>
  <Toolbar :bar>
    <template #command="{ command, variant, run }">
      <ToolbarButton :command :variant @click="run()" />
    </template>
    <template #custom="{ name, variant }">
      <PageNumberInput v-if="name === 'page-number'" :compact="variant === 'compact'" />
    </template>
  </Toolbar>
</template>
```

**`App.vue`**

```vue
<script setup lang="ts">
import { ref } from 'vue';
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 { commandsPlugin, standardCommands } from '@embedpdf/vue/commands';
import { Toolbar } from '@embedpdf/vue/toolbar';
import { localEngine } from '@embedpdf/engine';
import GoToPage from './GoToPage.vue';
import PageNumber from './PageNumber.vue';
import { ICONS, bar } from './bar';

import '../custom-item.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin(), commandsPlugin({ commands: standardCommands })];

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

const width = ref(100);
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <label class="width">
        Toolbar width
        <input v-model.number="width" type="range" min="30" max="100" />
        <output>{{ width }}%</output>
      </label>
      <div class="frame" :style="{ width: `${width}%` }">
        <Toolbar :bar="bar">
          <template #command="{ command, variant, run }">
            <button
              type="button"
              class="button"
              :title="command.label"
              :aria-label="command.label"
              :disabled="!command.enabled"
              @click="run()"
            >
              <span aria-hidden="true">{{ ICONS[command.icon ?? ''] ?? '…' }}</span>
              <span v-if="variant === 'icon+label'">{{ command.label }}</span>
            </button>
          </template>
          <template #custom="{ name, variant }">
            <PageNumber v-if="name === 'page-number'" :compact="variant === 'compact'" />
          </template>
        </Toolbar>
      </div>
      <GoToPage />
      <Stage class="stage">
        <template #page><RenderLayer /></template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>
```

**`custom-item.css`**

```css
/* Colors, and the toolbar's own parts (its "More" menu) to match */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);

  --epdf-toolbar-surface: var(--surface);
  --epdf-toolbar-border: var(--line);
  --epdf-toolbar-active: var(--tint);
}

/* The width slider */
.width {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  font-size: 13px;
  color: var(--muted);
}

.width input {
  flex: 1;
  max-width: 220px;
  accent-color: var(--accent);
}

.width output {
  min-width: 40px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* The toolbar, above the pages */
.frame {
  margin-bottom: 14px;
  padding: 6px;
  box-sizing: border-box;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 1px 2px var(--shadow);
  color: var(--ink);
  font-size: 13px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 32px;
  min-width: 32px;
  font: inherit;
  font-weight: 550;
  white-space: nowrap;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 0 8px;
  cursor: pointer;
}

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

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

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

/* The page number: your own item */
.page-number {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.page-input {
  width: 3ch;
  font: inherit;
  font-weight: 600;
  text-align: center;
  color: var(--ink);
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 4px 6px;
}

.page-input:focus-visible {
  outline: none;
  border-color: var(--focus);
  box-shadow: 0 0 0 3px rgb(125 182 255 / 0.35);
}

/* "Go to page", opened from the "More" menu */
.go-to {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  padding: 10px 12px;
  font-size: 13px;
  background: var(--tint);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
}

/* The pages */
.stage {
  height: 420px;
  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);
}
```

**`bar.ts`**

```typescript
import { custom, group, item } from '@embedpdf/vue/toolbar';
import type { BarSchema } from '@embedpdf/vue/toolbar';

// The page number is an item of your own: it gets smaller first, and in the "More" menu it's the
// 'page:go-to' command.
export const bar: BarSchema = {
  id: 'main',
  sections: {
    start: [group('zoom', ['zoom:out', 'zoom:in', item('zoom:fit-width', { importance: 2 })])],
    center: [
      group('pages', [
        'page:previous',
        custom('page-number', 'page:go-to', { variants: ['full', 'compact'] }),
        'page:next',
      ]),
    ],
    end: [group('document', [item('document:download', { variants: ['icon+label', 'icon'] })])],
  },
};

export const ICONS: Record<string, string> = {
  'previous-page': '‹',
  'next-page': '›',
  'zoom-out': '−',
  'zoom-in': '+',
  'fit-width': '↔',
  download: '↓',
};
```

**`GoToPage.vue`**

```vue
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { useCommands } from '@embedpdf/vue/commands';
import PageNumber from './PageNumber.vue';

// 'page:go-to', a command of your own: it opens a small "Go to page" form.
const commands = useCommands();
const open = ref(false);

let unregister: (() => void) | undefined;
onMounted(() => {
  unregister = commands.registerCommand({
    id: 'page:go-to',
    label: 'Go to page…',
    run: () => {
      open.value = true;
    },
  });
});
onUnmounted(() => unregister?.());
</script>

<template>
  <div v-if="open" class="go-to" role="dialog" aria-label="Go to page">
    <PageNumber :compact="false" />
    <button type="button" class="button" @click="open = false">Done</button>
  </div>
</template>
```

**`PageNumber.vue`**

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { useStage, useStageState } from '@embedpdf/vue/stage';

// The item itself: "Page 3 of 120", or "3 / 120" when there's less room.
defineProps<{ compact: boolean }>();

const stage = useStage();
const { currentPageIndex, pageCount } = useStageState();
const typed = ref<string | null>(null);

function onFocus(event: FocusEvent) {
  (event.target as HTMLInputElement).select();
}

function onInput(event: Event) {
  typed.value = (event.target as HTMLInputElement).value;
}

function onKeyDown(event: KeyboardEvent) {
  if (event.key !== 'Enter') return;
  const number = Number(typed.value);
  if (Number.isInteger(number) && number >= 1) stage.goToPage(number - 1);
  typed.value = null;
}
</script>

<template>
  <label class="page-number">
    <template v-if="!compact">Page</template>
    <input
      class="page-input"
      inputmode="numeric"
      aria-label="Page number"
      :value="typed ?? String(currentPageIndex + 1)"
      @focus="onFocus"
      @input="onInput"
      @blur="typed = null"
      @keydown="onKeyDown"
    />
    {{ compact ? `/ ${pageCount}` : `of ${pageCount}` }}
  </label>
</template>
```

The second argument, `'page:go-to'` (a command of your own that opens a "Go to page" dialog), is what
the item becomes in the "More" menu when there's no room for it. `variants` lets it get smaller first, like a command's variants.

The `#custom` slot draws every custom item, by `name`. Where it draws nothing for one (a `v-if` that
doesn't match), the item's command shows as a button instead. Without the slot, each item is a native
`<slot name>` element with that button inside: in a web component, the host's children for that slot
show there.

The first element in your item that takes focus is where the toolbar's arrow keys stop. The rest of
its controls are your item's own, and a text field keeps Left and Right for its caret.

## Draw every part

| Prop                | Draws                                                                  |
| :------------------ | :--------------------------------------------------------------------- |
| `#command`          | A button, for a command and its variant                                |
| `#custom`           | Your own items, by name                                                |
| `#collapsed`        | A folded group: its button and its menu                                |
| `#separator`        | The line between groups                                                |
| `#overflow-trigger` | The "More" button                                                      |
| `#overflow-menu`    | The "More" menu, with its sections, items and the call to run each one |

Every slot you leave out has a plain default, so you can start with `#command` and add the rest as
you style them. A slot that draws nothing for a part leaves that part to the default. `gap` and
`separator-width` set the spacing, and `class` and `style` go on the toolbar itself. `orientation`
stands it on its side ([Sections](#sections)), and `aria-label` names it for a screen reader when the
page has more than one toolbar.

Whatever you draw, the toolbar is one stop for the Tab key. The arrow keys along it move between its
items, past disabled ones, and Home and End go to its ends. The default menus open on their first
row: the arrow keys move, typing a letter jumps to the row that starts with it, Enter picks, and
Escape closes the menu and puts focus back on its button.

## A toolbar per mode

Most viewers change the toolbar with what people do: annotation tools while annotating, field tools
while building a form. Keep a bar per mode and pick one from your state:

**`toolbar-mode.vue`**

```vue
<script setup lang="ts">
import { computed } from 'vue';
import { useInteractionState } from '@embedpdf/vue/interaction';
import { Toolbar } from '@embedpdf/vue/toolbar';
import { formBar, mainBar } from './bars';
import ToolbarButton from './ToolbarButton.vue';

const { activeToolId } = useInteractionState();
const bar = computed(() => (activeToolId.value === 'form-edit' ? formBar : mainBar));
</script>

<template>
  <Toolbar :bar>
    <template #command="{ command, variant, run }">
      <ToolbarButton :command :variant @click="run()" />
    </template>
  </Toolbar>
</template>
```

**`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 { interactionPlugin } from '@embedpdf/vue/interaction';
import { SelectionLayer, selectionPlugin } from '@embedpdf/vue/selection';
import { AnnotationLayer, annotationPlugin } from '@embedpdf/vue/annotation';
import { commandsPlugin, standardCommands } from '@embedpdf/vue/commands';
import { localEngine } from '@embedpdf/engine';
import ModeToolbar from './ModeToolbar.vue';

import '../modes.css';

const engine = localEngine();
const plugins = [
  stagePlugin(),
  renderPlugin(),
  interactionPlugin(),
  selectionPlugin(),
  annotationPlugin(),
  commandsPlugin({ commands: standardCommands }),
];

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

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <ModeToolbar />
      <Stage class="stage">
        <template #page>
          <RenderLayer />
          <SelectionLayer />
          <AnnotationLayer />
        </template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>
```

**`modes.css`**

```css
/* Colors, and the toolbar's own parts (its "More" menu) to match */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);

  --epdf-toolbar-surface: var(--surface);
  --epdf-toolbar-border: var(--line);
  --epdf-toolbar-active: var(--tint);
}

/* The mode tabs and the mode's toolbar, above the pages */
.chrome {
  margin-bottom: 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 1px 2px var(--shadow);
  color: var(--ink);
  font-size: 13px;
}

.modes {
  display: flex;
  gap: 4px;
  padding: 6px 6px 0;
  border-bottom: 1px solid var(--line);
}

.mode {
  font: inherit;
  font-weight: 600;
  color: var(--muted);
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 6px 10px 8px;
  cursor: pointer;
}

.mode[aria-selected='true'] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

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

.toolbar {
  padding: 6px;
}

.button {
  height: 32px;
  font: inherit;
  font-weight: 550;
  white-space: nowrap;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 0 10px;
  cursor: pointer;
}

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

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

/* The active tool shows as pressed */
.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

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

/* The pages */
.stage {
  height: 420px;
  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);
}
```

**`ModeToolbar.vue`**

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { useInteraction } from '@embedpdf/vue/interaction';
import { Toolbar, group } from '@embedpdf/vue/toolbar';
import type { BarSchema } from '@embedpdf/vue/toolbar';

// A bar per mode.
const viewBar: BarSchema = {
  id: 'view',
  sections: {
    start: [group('tools', ['tool:pointer', 'tool:pan'])],
    center: [
      group('pages', ['page:previous', 'page:next']),
      group('zoom', ['zoom:out', 'zoom:in']),
    ],
  },
};

const annotateBar: BarSchema = {
  id: 'annotate',
  sections: {
    start: [group('markup', ['tool:highlight', 'tool:underline', 'tool:strikeout'])],
    center: [group('draw', ['tool:ink', 'tool:square', 'tool:circle', 'tool:note'])],
    end: [group('edit', ['annotation:delete'])],
  },
};

type Mode = 'view' | 'annotate';
const BARS: Record<Mode, BarSchema> = { view: viewBar, annotate: annotateBar };
const TOOL_OF_MODE: Record<Mode, string> = { view: 'pointer', annotate: 'highlight' };
const MODES = ['view', 'annotate'] as const;

const interaction = useInteraction();
const mode = ref<Mode>('view');

// Each mode starts with its own tool.
function switchTo(next: Mode) {
  mode.value = next;
  interaction.activateTool(TOOL_OF_MODE[next]);
}
</script>

<template>
  <div class="chrome">
    <div class="modes" role="tablist" aria-label="Mode">
      <button
        v-for="each in MODES"
        :key="each"
        type="button"
        role="tab"
        class="mode"
        :aria-selected="mode === each"
        @click="switchTo(each)"
      >
        {{ each === 'view' ? 'View' : 'Annotate' }}
      </button>
    </div>
    <Toolbar :bar="BARS[mode]" class="toolbar">
      <template #command="{ command, run }">
        <button
          type="button"
          class="button"
          :disabled="!command.enabled"
          :aria-pressed="command.active"
          @click="run()"
        >
          {{ command.label }}
        </button>
      </template>
    </Toolbar>
  </div>
</template>
```

## Good to know

### Commands keep the buttons right

Because the items are commands, a button disables itself when its command can't run, shows as pressed
when it's active, and hides when the command isn't visible, with no code in the toolbar. Change a
permission or open a document, and the toolbar follows.

A group with `dependent: true`, like undo and redo, shows only while something in the bar that isn't
dependent shows: a bar left with only those draws nothing.

### Labels in the user's language

A command's label comes through [translations](translations), so the toolbar is in the user's language
and re-measures when it changes.

### The default look

When you don't draw a part yourself, the toolbar draws a plain one. Its colors come from CSS only,
because you're meant to replace them: `--epdf-toolbar-surface` (the "More" menu and folded groups,
`#ffffff`), `--epdf-toolbar-border` (`rgb(0 0 0 / 0.15)`) and `--epdf-toolbar-active` (a pressed or
open button, `rgb(0 0 0 / 0.12)`).
