---
title: "Toolbar — React"
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: "React"
source: "https://www.embedpdf.com/docs/headless/react/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.tsx`**

```tsx
import { Toolbar, group, item } from '@embedpdf/react/toolbar';
import { Icon } from './icon';

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' })],
  },
};

export function MainToolbar() {
  return (
    <Toolbar
      bar={bar}
      renderCommand={(command, variant, run) => (
        <button onClick={run} disabled={!command.enabled} aria-pressed={command.active} title={command.label}>
          <Icon name={command.icon} />
          {variant === 'icon+label' && command.label}
        </button>
      )}
    />
  );
}
```

**`basic.tsx`**

```tsx
import { useState } from 'react';
import { Viewer, DocumentGate } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { Stage, stagePlugin } from '@embedpdf/react/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { interactionPlugin } from '@embedpdf/react/interaction';
import { SelectionLayer, selectionPlugin } from '@embedpdf/react/selection';
import { AnnotationLayer, annotationPlugin } from '@embedpdf/react/annotation';
import { commandsPlugin, standardCommands } from '@embedpdf/react/commands';
import { Toolbar, group, item } from '@embedpdf/react/toolbar';
import type { BarSchema } from '@embedpdf/react/toolbar';
import { localEngine } from '@embedpdf/engine';

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()) };
};

// 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']),
    ],
  },
};

function MainToolbar() {
  return (
    <Toolbar
      bar={bar}
      className="toolbar"
      renderCommand={(command, variant, run) => (
        <button
          type="button"
          className="button"
          title={command.label}
          aria-label={command.label}
          disabled={!command.enabled}
          aria-pressed={command.active}
          onClick={run}
        >
          <span className="icon" aria-hidden>
            {ICONS[command.icon ?? ''] ?? command.label.charAt(0)}
          </span>
          {variant === 'icon+label' && <span>{command.label}</span>}
        </button>
      )}
    />
  );
}

export default function App() {
  // Narrow the toolbar to watch it make room: labels go first, then the tools fold, then "More".
  const [width, setWidth] = useState(100);

  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <DocumentGate fallback={<p className="loading">Loading…</p>}>
        <label className="width">
          Toolbar width
          <input
            type="range"
            min={30}
            max={100}
            value={width}
            onChange={(event) => setWidth(Number(event.target.value))}
          />
          <output>{width}%</output>
        </label>
        <div className="frame" style={{ width: `${width}%` }}>
          <MainToolbar />
        </div>
        <Stage className="stage">
          {() => (
            <>
              <RenderLayer />
              <SelectionLayer />
              <AnnotationLayer />
            </>
          )}
        </Stage>
      </DocumentGate>
    </Viewer>
  );
}
```

**`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);
}
```

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

```tsx
import { Toolbar, group } from '@embedpdf/react/toolbar';
import { Icon } from './icon';

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

export function ToolRail() {
  return (
    <Toolbar
      bar={rail}
      orientation="vertical"
      aria-label="Tools"
      style={{ height: '100%' }}
      renderCommand={(command, _variant, run) => (
        <button onClick={run} disabled={!command.enabled} aria-pressed={command.active} title={command.label}>
          <Icon name={command.icon} />
        </button>
      )}
    />
  );
}
```

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

```tsx
import { Toolbar, custom, group } from '@embedpdf/react/toolbar';
import { PageNumberInput } from './page-number-input';
import { renderCommand } from './render-command';

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

export function PageToolbar() {
  return (
    <Toolbar
      bar={bar}
      renderCommand={renderCommand}
      renderCustom={{ 'page-number': (variant) => <PageNumberInput compact={variant === 'compact'} /> }}
    />
  );
}
```

**`custom-item.tsx`**

```tsx
import { useEffect, useState } from 'react';
import { Viewer, DocumentGate } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { Stage, stagePlugin, useStage, useStageState } from '@embedpdf/react/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { commandsPlugin, standardCommands, useCommands } from '@embedpdf/react/commands';
import { Toolbar, custom, group, item } from '@embedpdf/react/toolbar';
import type { BarSchema } from '@embedpdf/react/toolbar';
import type { ResolvedCommand } from '@embedpdf/react/commands';
import { localEngine } from '@embedpdf/engine';

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()) };
};

// 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.
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'] })])],
  },
};

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

const renderCommand = (command: ResolvedCommand, variant: string, run: () => void) => (
  <button
    type="button"
    className="button"
    title={command.label}
    aria-label={command.label}
    disabled={!command.enabled}
    onClick={run}
  >
    <span aria-hidden>{ICONS[command.icon ?? ''] ?? '…'}</span>
    {variant === 'icon+label' && <span>{command.label}</span>}
  </button>
);

// The item itself: "Page 3 of 120", or "3 / 120" when there's less room.
function PageNumber({ compact }: { compact: boolean }) {
  const stage = useStage();
  const { currentPageIndex, pageCount } = useStageState();
  const [typed, setTyped] = useState<string | null>(null);

  return (
    <label className="page-number">
      {!compact && 'Page'}
      <input
        className="page-input"
        inputMode="numeric"
        aria-label="Page number"
        value={typed ?? String(currentPageIndex + 1)}
        onFocus={(event) => event.target.select()}
        onChange={(event) => setTyped(event.target.value)}
        onBlur={() => setTyped(null)}
        onKeyDown={(event) => {
          if (event.key !== 'Enter') return;
          const number = Number(typed);
          if (Number.isInteger(number) && number >= 1) stage.goToPage(number - 1);
          setTyped(null);
        }}
      />
      {compact ? `/ ${pageCount}` : `of ${pageCount}`}
    </label>
  );
}

// 'page:go-to', a command of your own: it opens a small "Go to page" form.
function GoToPage() {
  const commands = useCommands();
  const [open, setOpen] = useState(false);

  useEffect(
    () =>
      commands.registerCommand({
        id: 'page:go-to',
        label: 'Go to page…',
        run: () => setOpen(true),
      }),
    [commands],
  );

  if (!open) return null;
  return (
    <div className="go-to" role="dialog" aria-label="Go to page">
      <PageNumber compact={false} />
      <button type="button" className="button" onClick={() => setOpen(false)}>
        Done
      </button>
    </div>
  );
}

export default function App() {
  const [width, setWidth] = useState(100);

  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <DocumentGate fallback={<p className="loading">Loading…</p>}>
        <label className="width">
          Toolbar width
          <input
            type="range"
            min={30}
            max={100}
            value={width}
            onChange={(event) => setWidth(Number(event.target.value))}
          />
          <output>{width}%</output>
        </label>
        <div className="frame" style={{ width: `${width}%` }}>
          <Toolbar
            bar={bar}
            renderCommand={renderCommand}
            renderCustom={{
              'page-number': (variant) => <PageNumber compact={variant === 'compact'} />,
            }}
          />
        </div>
        <GoToPage />
        <Stage className="stage">{() => <RenderLayer />}</Stage>
      </DocumentGate>
    </Viewer>
  );
}
```

**`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);
}
```

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.

`renderCustom` has one renderer per item name. A renderer that returns `undefined` draws the item's
command as a button instead. An item with no renderer 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                                                                  |
| :---------------------- | :--------------------------------------------------------------------- |
| `renderCommand`         | A button, for a command and its variant                                |
| `renderCustom`          | Your own items, by name                                                |
| `renderCollapsed`       | A folded group: its button and its menu                                |
| `renderSeparator`       | The line between groups                                                |
| `renderOverflowTrigger` | The "More" button                                                      |
| `renderOverflowMenu`    | The "More" menu, with its sections, items and the call to run each one |

Every prop you leave out has a plain default, so you can start with `renderCommand` and add the rest as
you style them. `renderCollapsed` and `renderGroupTrigger` can return `undefined` for a group to leave
it to the default. `gap` and `separatorWidth` set the spacing, and `className` 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.tsx`**

```tsx
import { useInteractionState } from '@embedpdf/react/interaction';
import { Toolbar } from '@embedpdf/react/toolbar';
import { formBar, mainBar } from './bars';
import { renderCommand } from './render-command';

export function ModeToolbar() {
  const { activeToolId } = useInteractionState();
  const bar = activeToolId === 'form-edit' ? formBar : mainBar;

  return <Toolbar bar={bar} renderCommand={renderCommand} />;
}
```

**`modes.tsx`**

```tsx
import { useState } from 'react';
import { Viewer, DocumentGate } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { Stage, stagePlugin } from '@embedpdf/react/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { interactionPlugin, useInteraction } from '@embedpdf/react/interaction';
import { SelectionLayer, selectionPlugin } from '@embedpdf/react/selection';
import { AnnotationLayer, annotationPlugin } from '@embedpdf/react/annotation';
import { commandsPlugin, standardCommands } from '@embedpdf/react/commands';
import type { ResolvedCommand } from '@embedpdf/react/commands';
import { Toolbar, group } from '@embedpdf/react/toolbar';
import type { BarSchema } from '@embedpdf/react/toolbar';
import { localEngine } from '@embedpdf/engine';

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()) };
};

// 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 renderCommand = (command: ResolvedCommand, _variant: string, run: () => void) => (
  <button
    type="button"
    className="button"
    disabled={!command.enabled}
    aria-pressed={command.active}
    onClick={run}
  >
    {command.label}
  </button>
);

function ModeToolbar() {
  const interaction = useInteraction();
  const [mode, setMode] = useState<Mode>('view');

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

  return (
    <div className="chrome">
      <div className="modes" role="tablist" aria-label="Mode">
        {(['view', 'annotate'] as const).map((each) => (
          <button
            key={each}
            type="button"
            role="tab"
            className="mode"
            aria-selected={mode === each}
            onClick={() => switchTo(each)}
          >
            {each === 'view' ? 'View' : 'Annotate'}
          </button>
        ))}
      </div>
      <Toolbar bar={BARS[mode]} className="toolbar" renderCommand={renderCommand} />
    </div>
  );
}

export default function App() {
  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <DocumentGate fallback={<p className="loading">Loading…</p>}>
        <ModeToolbar />
        <Stage className="stage">
          {() => (
            <>
              <RenderLayer />
              <SelectionLayer />
              <AnnotationLayer />
            </>
          )}
        </Stage>
      </DocumentGate>
    </Viewer>
  );
}
```

**`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);
}
```

## 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)`).
