---
title: "Layout — Vanilla JS"
description: "Put toolbars at any edge, several at once or floating over the pages, and add, move, replace or remove any button."
integration: "Vanilla JS"
source: "https://www.embedpdf.com/docs/viewer/vanilla/customize/layout"
---

# Layout

The layout says where every toolbar, button, menu and panel goes. Give `layout` a function: it gets
the default layout and returns yours, one change per line.

```js
{
  layout: (layout) => layout.place('main', 'bottom'),
}
```

That moves the main toolbar to the bottom. Its menus now open upward, the mode tools sit above it,
and nothing else changes. Every change returns a new layout, so changes chain:

```js
{
  layout: (layout) =>
    layout
      .place('main', 'bottom')
      .remove(['tool:pan', 'panel:comments'], { from: 'main' })
      .add('document:print', { to: 'main', section: 'end' }),
}
```

## The default layout

| Bar                                                         | Where                       | What's in it                                                                                                          |
| :---------------------------------------------------------- | :-------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| `main`                                                      | `top`                       | The document menu, thumbnails, view menu, zoom and tools; the mode tabs in the middle; search and comments at the end |
| `annotate`, `shapes`, `insert`, `form`, `redact`, `measure` | Along `main`, one at a time | The tools of the mode whose tab is selected                                                                           |
| `pager`                                                     | Floating at the bottom      | The page number between the previous and next page buttons; nothing for a document of one page                        |
| `selection`                                                 | Over selected text          | Copy, highlight, underline, strike out                                                                                |
| `annotation`                                                | Over selected annotations   | Comment, style, delete                                                                                                |
| `thumbnails`                                                | In the thumbnails panel     | Insert, rotate and delete pages, and undo and redo                                                                    |
| `outline`                                                   | In the outline panel        | The filter; add, expand all and collapse all                                                                          |
| `attachments`                                               | In the attachments panel    | How many files there are, and attach a file                                                                           |

The ids of every bar, group, menu and button are at the [end of this page](#every-built-in-id).

## Move a toolbar

`place(bar, place)` moves a bar. A place is one of these:

| Place                                         | Where the bar goes                                                                                                                                                                                                                 |
| :-------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'top'`, `'bottom'`                           | Along the top or bottom edge, horizontal                                                                                                                                                                                           |
| `'start'`, `'end'`                            | Along the left or right edge, vertical. In a right-to-left language they swap sides.                                                                                                                                               |
| `{ float: 'bottom' }`                         | Floating over the pages: `'top-start'`, `'top'`, `'top-end'`, `'start'`, `'end'`, `'bottom-start'`, `'bottom'` or `'bottom-end'`. Add `hideWhenIdle` to [rest it out of sight while nothing moves](#out-of-the-way-while-reading). |
| `{ with: 'main' }`                            | Along another bar, on the side of the pages. The mode bars ride along `main` this way, so they follow it wherever it goes.                                                                                                         |
| `{ at: 'selection' }`, `{ at: 'annotation' }` | Over selected text, or over selected annotations                                                                                                                                                                                   |
| `{ panel: 'thumbnails' }`                     | At the top of a panel, under its title: any panel's id, your own too. See [A bar in a panel](panels#a-bar-in-a-panel).                                                                                                             |

You never say which way a menu opens or where a tooltip goes. A menu opens away from its bar's
edge, toward the pages, and flips when there's no room. A vertical bar shows icons, with the
labels as tooltips.

## Several toolbars

Add a bar with `addBar(id, bar)`. This one puts the drawing tools in a rail on the left, next to
the main toolbar at the top:

```js
{
  layout: (layout) =>
    layout.addBar('tools', {
      place: 'start',
      start: [
        ['tool:pointer', 'tool:pan'],
        ['tool:highlight', 'tool:underline', 'tool:ink', 'tool:note'],
        ['history:undo', 'history:redo'],
      ],
    }),
}
```

Several bars can share an edge. They stack from the outside in, in the order you add them. To put a
bar in a particular spot, say which bar it goes before or after:

```js
layout.addBar('review', {
  place: { edge: 'top', after: 'main' },
  center: [['acme:approve', 'acme:reject']],
});
```

## A floating toolbar

A bar can float over the pages, as the page number does by default. This one puts the zoom field,
with its out and in buttons, in the bottom-right corner:

```js
{
  layout: (layout) =>
    layout.addBar('zoom', {
      place: { float: 'bottom-end' },
      center: [[{ custom: 'zoom' }]],
    }),
}
```

A floating bar has a shadow, keeps clear of the scrollbars, and sits above the pages but below
menus and panels.

It sits over the pages, so in "fit page" a page's edge can be under it. Give the pages room on that
side with the Stage's `padding`, which takes each side on its own:

```js
{
  plugins: { stage: { padding: { top: 24, right: 24, bottom: 72, left: 24 } } },
}
```

Below 600 px wide the Stage's own rule makes the padding `4` on every side: see
[Adapt to the space you have](https://www.embedpdf.com/docs/headless/react/viewing/stage#adapt-to-the-space-you-have) to set your
own.

Bars floating on one edge share it: the corner ones sit in the corners, and the one in the middle
stays centered over the pages, moving aside only as far as a corner bar needs. When it doesn't fit
beside them, the corner bars move one row in, toward the middle of the pages, so no bar covers
another. On a phone, the zoom above and the page number sit side by side when they fit, and one
over the other when they don't.

Here the main toolbar is at the bottom, and the zoom field has left it for a floating bar of its
own:

**`bottom-bar.html`**

```html
<embedpdf-viewer id="viewer" style="height: 100%"></embedpdf-viewer>

<script type="module">
  import 'https://cdn.jsdelivr.net/npm/@embedpdf/viewer@3/dist/embedpdf.js';

  const welcome = '/pdfs/welcome.pdf';

  document.getElementById('viewer').config = {
    src: welcome,
    layout: (layout) =>
      layout
        .place('main', 'bottom')
        .remove('zoom', { from: 'main' })
        .addBar('zoom', {
          place: { float: 'bottom-end' },
          center: [[{ custom: 'zoom' }]],
        }),
  };
</script>
```

> **Try it:** zoom in on the timeline on page 3 from the floating buttons at the bottom right, then
> open the menu at the start of the toolbar: it opens upward.

### Out of the way while reading

`hideWhenIdle: true` fades a floating bar out once the pages and the pointer have been still for
two seconds, and brings it back when the pages move, the pointer moves over them or touches them,
or the keyboard reaches the bar, at every size:

```js
layout.addBar('zoom', {
  place: { float: 'bottom-end', hideWhenIdle: true },
  center: [['zoom:out', 'zoom:in']],
});
```

`false` keeps a bar on screen. Without either, the bar with the page number rests out of sight like
this, and every other bar stays:

```js
layout.place('pager', { float: 'bottom', hideWhenIdle: false });
```

A bar stays while the pointer is on it, while you're using it from the keyboard or typing in it,
like the page number, and while a menu from it is open; a button you clicked doesn't keep it. Out of
sight, it takes no clicks, so a click there reaches the page, and it keeps its place in the
keyboard's order.

## Add a button

`add(item, { to })` puts an item in a bar or a menu. Without a position it goes at the end:

```js
layout.add('document:print', { to: 'main' });
layout.add('document:print', { to: 'main', section: 'end' });
layout.add('document:print', { to: 'main', after: 'panel:search' });
layout.add('document:print', { to: 'document' }); // the document menu
```

| Option            | What it is                                      | Default                               |
| :---------------- | :---------------------------------------------- | :------------------------------------ |
| `to`              | The bar or menu                                 | none                                  |
| `section`         | `'start'`, `'center'` or `'end'`                | `'end'`                               |
| `group`           | The group to join; a new id starts a new group  | a new group at the end of the section |
| `before`, `after` | An item to put it next to, in that item's group | none                                  |

An item is a command id, such as one of yours from [Buttons & commands](commands). It can also be
your own component: see [Your own components](components). A component goes by its id in every
change, the viewer's own components too: `remove('zoom', { from: 'main' })` takes out the zoom
field, `replace('zoom', { custom: 'zoom-pill' })` puts yours in its place, and
`{ to: 'pager', after: 'page-number' }` goes next to the page number.

## Remove buttons

`remove(ids, { from })` takes items out of one bar or menu, and a group with nothing left
disappears with its line:

```js
layout.remove(['tool:pan', 'menu:view'], { from: 'main' });
layout.remove('view:fullscreen', { from: 'document' });
```

The command itself stays: it can sit in another bar, its shortcut works, and your code can run it.
To leave something out everywhere at once, every button, menu row, tab and shortcut, use
[`disable`](../features/turning-off) instead: the layout says where things go, `disable` says
whether the viewer has them.

`removeBar(id)` removes a whole bar, with the bars that ride along it. Removing a mode's bar removes
its tab, while its tools stay available for a bar of your own. It takes a list too:

```js
layout.removeBar(['measure', 'pager']);
```

The mode bars ride along `main`, so `removeBar('main')` takes them with it: no main toolbar, and no
modes.

## Replace a button

`replace(id, item)` puts another item in its place, everywhere it appears:

```js
layout.replace('document:download', 'acme:save');
```

## Build a bar

A bar has three sections: `start` (left), `center` and `end` (right). The center stays centered in
the room the others leave, and in a right-to-left language `start` and `end` swap. Each section is
a list of groups, and a line separates the groups:

```js
layout.addBar('review', {
  place: 'bottom',
  start: [['acme:previous-issue', 'acme:next-issue']],
  center: [{ id: 'verdict', items: ['acme:approve', 'acme:reject'], collapse: 'menu' }],
  end: ['document:download'],
});
```

A group is an array of items, or an object when it needs a name or options:

| Field        | What it is                                                                                                  | Default |
| :----------- | :---------------------------------------------------------------------------------------------------------- | :------ |
| `id`         | Its name, so `add(item, { group })` can find it                                                             | none    |
| `items`      | The items                                                                                                   | none    |
| `collapse`   | Fold into one button with a menu (`'menu'`) or a dropdown (`'select'`) when there's no room                 | none    |
| `importance` | Who makes room first, `1` to `5`: the group, and each item without its own                                  | `3`     |
| `dependent`  | Show only while something in the bar that isn't `dependent` shows, like undo and redo beside a mode's tools | `false` |

A lone item in a section, like `'document:download'` above, is a group of one.

## When there's no room

A bar never wraps and never scrolls. When its items don't fit, it makes room in this order:

1. An item gets smaller: from its first variant to the next, such as icon and label to icon only.
2. A group with `collapse` folds into one button.
3. Items move into a "More" menu at the end of the bar, the least important first.

An item that needs more than a command id is an object:

```js
{ command: 'acme:share', variants: ['icon+label', 'icon'], importance: 5 }
```

`importance` goes from `1` (makes room first) to `5` (never leaves the bar). An item without one has
its group's, so `importance` on a group is for each of its items too. The bar measures what it
really draws, so a longer translation or a bigger font just works.

## A primary button

The one thing a bar is for, such as Approve or Sign, can stand out: `emphasis: 'primary'` draws
its button filled in the accent color.

```js
layout.add(
  { command: 'acme:approve', emphasis: 'primary', variants: ['icon+label', 'icon'] },
  { to: 'main', section: 'end' },
);
```

In the "More" menu it's a row like the others. Its part is `toolbar-button-primary`, and each bar
is the part `toolbar-<id>` too, so CSS can give one bar a look of its own: see
[Theming](theming#restyle-a-part).

## A new mode

The mode tabs list every bar that has a `mode`. Add one, and a tab appears:

```js
layout.addBar('review', {
  place: { with: 'main' },
  mode: 'review',
  title: 'Review',
  center: [['acme:approve', 'acme:reject', 'acme:request-changes']],
});
```

The tab's command is `mode:review`, so a button of your own can open the mode too.

## Your whole layout

Instead of a function, `layout` can be a layout you wrote from scratch: bars, menus and panels,
each by id.

```js
{
  layout: {
    bars: {
      main: {
        place: 'top',
        start: [[{ custom: 'zoom' }]],
        end: [['panel:search', 'document:download']],
      },
    },
    panels: {
      search: { side: 'end' },
    },
  },
}
```

That's a reading room: zoom, search and download, nothing else. A built-in panel's id brings its
own title, icon and words, so `search` needs no more than its side. Which one to choose:

- **A function** builds on the default, so a new version of the viewer brings its new buttons with it.
- **A whole layout** stays exactly as you wrote it. New features arrive as new ids you add when you
  want them.

## Good to know

### Every built-in id

**Bars and their groups**

| Bar           | Groups                                                                                 |
| :------------ | :------------------------------------------------------------------------------------- |
| `main`        | `document`, `view`, `zoom`, `tools` in `start`; `modes` in `center`; `panels` in `end` |
| `annotate`    | `comment`, `markup`, `draw`, `text`, `style`, `history`                                |
| `shapes`      | `shapes`, `polygons`, `style`, `history`                                               |
| `insert`      | `stamps`, `style`, `history`                                                           |
| `form`        | `fields`, `choices`, `buttons`, `signatures`, `history`                                |
| `redact`      | `redact`, `style`, `history`                                                           |
| `measure`     | `measure`, `scale`, `style`                                                            |
| `pager`       | `pages`                                                                                |
| `selection`   | `selection`                                                                            |
| `annotation`  | `actions`, `danger`                                                                    |
| `thumbnails`  | `edit`, `more`, `history`                                                              |
| `outline`     | `filter`, `items`                                                                      |
| `attachments` | `count`, `files`                                                                       |

**Menus**: `document`, `view`, `zoom`, `outline-item` (an outline row's), `attachment-item` (a
file's, in the attachments panel), `comment-item` (a card's, in the comments panel) and `page` (a
page's, in the thumbnails panel). Their buttons are `menu:document`, `menu:view`, `menu:zoom`,
`menu:outline-item`, `menu:attachment-item`, `menu:comment-item` and `menu:page`. See [Menus](menus).

**Your own components the viewer ships**: `{ custom: 'zoom' }` (the zoom field),
`{ custom: 'page-number' }` (the page number and count, "3 / 10", one button that turns into a field
to type a page, and both numbers while a spread fits across the view: "2–3 / 10"; nothing for a
document of one page), `{ custom: 'quick-stamps' }` (the stamps
bar of the Insert mode), `{ custom: 'measurement-scale' }` (the scale of the Measure mode),
`{ custom: 'view-only' }` (the [View only](../features/turning-off#read-only) label, in the main bar's
`panels` group), `{ custom: 'outline-filter' }` (the outline's filter field, in its panel's bar) and
`{ custom: 'attachments-count' }` (how many files the document carries).

**Buttons**: every command, listed with what it does on [Buttons & commands](commands#every-built-in-command).

### What the user may not use

A button for something the user [may not do](../features/turning-off#what-a-user-may-do), or that
you [turned off](../features/turning-off), isn't in the bar, and a group,
bar, mode tab or menu with nothing left disappears. The `style` and `history` groups of the mode
bars are `dependent`, so a mode bar left with only style, undo and redo disappears too. Your layout
stays the same: give the user the permission, and everything is back where you put it.

### Ids are kept

The ids on this page are part of the API. They don't change between minor versions, so a layout
function written today keeps working.

### Checked as you go

A layout that names an id the viewer doesn't have, such as a typo in `remove()`, logs a warning with
the id and the closest one that exists. The viewer still starts.
