Panels
Panels hold what sits next to the pages: thumbnails, the outline, search results, comments. Each
one has a button, panel:<id>, and a side. Move one with panel(id, changes):
{
layout: (layout) => layout.panel('thumbnails', { side: 'end' }),
}The built-in panels#
| Panel | What’s in it | Side | Opens with |
|---|---|---|---|
thumbnails | A small picture of every page; pick one to go there, or organize the pages | start | panel:thumbnails |
outline | The document’s table of contents (its bookmarks): follow, filter, and with the right to change the outline, add, rename, drag and delete items | start | panel:outline |
attachments | The files inside the PDF, its own and those pinned to its pages: save them, go to a page’s file, and with the right to, attach and remove files | start | panel:attachments |
search | Find text, and every match in context; a find bar where copying isn’t allowed | end | panel:search, search:find (Ctrl/Cmd+F) |
comments | Every comment and its replies | end | panel:comments |
style | Color, width and font of the selected annotation, or of the tool | end | panel:style |
stamps | Stamp libraries; pick one, then click the page | end | panel:stamps |
signatures | Saved signatures and initials; the document’s signatures | end | panel:signatures |
redaction | Marked areas waiting to be removed | end | panel:redaction |
measurement | The scale, and every measurement on the page | end | panel:measurement |
form-design | The form’s fields, and the selected one’s settings and buttons; in the Form mode only | end | form:design, panel:form-design |
Each one comes with its title, its icon and what goes inside, wherever its id is: a whole
layout with panels: { search: { side: 'end' } } has the search panel
as it is.
A panel whose feature you turn off is gone, with its button. The outline and attachments panels show while they have a job: the document has items or files to list, or the user may add some. A read-only viewer on a document without an outline shows no outline button; the same for attachments.
A panel’s title#
A panel’s title is its button’s label too: one word for both. Rename them per language with the
button’s command id in strings:
{ strings: { en: { 'panel:comments': 'Review notes' } } }Or give the panel a title, the same in every language, or a titleKey for words of your own per
language. Either one replaces the built-in words, on the button and in the header:
layout.panel('comments', { title: 'Review notes' });A string for panel:comments comes first, over the layout’s title.
How a panel opens#
A panel opens in one of three ways:
present | What it does |
|---|---|
'dock' | Beside the pages, which make room for it |
'overlay' | Over the edge of the pages, which stay where they are and dim; a click on them closes it. The toolbars keep working. |
'sheet' | Up from the bottom of the viewer, over everything |
By default a panel is a sheet on a phone, an overlay at medium widths, and docks when the viewer
is wide: { compact: 'sheet', medium: 'overlay', expanded: 'dock' }. Give present one way for
every size, or a way for the sizes you name; the other sizes keep the default:
{
layout: (layout) =>
layout
// Every panel docks at medium widths too: never an overlay.
.panels({ present: { medium: 'dock' } })
// The thumbnails open over the pages on a phone, not as a sheet.
.panel('thumbnails', { present: { compact: 'overlay' } })
// The comments always open over the pages.
.panel('comments', { present: 'overlay' }),
}panels(changes) changes every panel the layout has so far, the way panel(id, changes) changes
one. A map of sizes changes only the sizes it names, so the thumbnails above still dock at medium
widths. The sizes go by the viewer’s own width: see Small screens.
A sheet is as wide as the viewer on a phone. On a wider viewer it is as wide as the panel’s
width, in the middle.
Search without copying#
Where the document’s permissions let people search but not copy text, search opens as a find bar
instead. The panel would list each match with the words around it, and showing those words is
copying them, so there would be nothing to list. The bar sits over the top corner of the pages on
the panel’s side, and across them on a phone. It shows which match you’re on and how many there
are. Enter goes to the next match, Shift+Enter to the one before, and its options sit behind
a button. It has the same button and the same Ctrl/Cmd+F, and keeps the same search, but
present doesn’t apply to it.
Width#
A panel starts 288 px wide. People can drag its edge, docked or over the pages, between 200 and
640 px and never past half the viewer, and the viewer keeps that width while it runs. On the edge,
the arrow keys move it, four times as far with Shift; Home makes the panel its narrowest and
End its widest. A sheet has no edge. Set where it starts, how far it drags, or no dragging at
all:
layout.panel('comments', { width: 360 }); // where it starts
layout.panel('comments', { width: { initial: 360, min: 280, max: 520 } }); // and how far it drags
layout.panel('comments', { resizable: false }); // no edge to dragpanels() takes them too: layout.panels({ resizable: false }) fixes every panel’s width.
Keep the width people chose#
onPanelResize tells you each width someone gives a panel: once a drag ends, and on each key. Keep
it where you like, and start the panel at it next time:
const saved = Number(localStorage.getItem('thumbnails-width'));
{
layout: (layout) => (saved ? layout.panel('thumbnails', { width: saved }) : layout),
onPanelResize: ({ id, width }) => {
if (id === 'thumbnails') localStorage.setItem('thumbnails-width', String(width));
},
}A number keeps the panel’s limits: it only says where it starts. The <embedpdf-viewer> element
fires epdf:panelresize with the same { id, width } as event.detail.
The thumbnails#
The thumbnails wrap: one column at the panel’s default width, and more as it gets wider. They are
a second Stage over the same document, so plugins.thumbnails
takes the Stage’s settings:
{
plugins: {
thumbnails: {
zoom: { pageWidth: 110 }, // each thumbnail 110 px wide; the default is 150
columns: 1, // always one column; 'auto', the default, wraps
},
},
}From code, viewer.get('thumbnails') is that Stage:
viewer.get('thumbnails').updateSettings({ columns: 2 }).
Organize pages#
Where the user may change the pages, the thumbnails panel is where they do it, as in most PDF apps:
- Select pages. A click selects a page and goes to it;
Ctrl/Cmd+click adds a page or takes it out,Shift+click selects a range. - The panel’s bar inserts a blank page, rotates and deletes the selected pages; with room to spare it also inserts a PDF, duplicates and extracts. Undo and redo sit at its end, and in a narrow panel what doesn’t fit is in its “More” menu.
- Each page has a menu: its button, a right-click, or a long press. It rotates, inserts a blank page or a PDF after the page, duplicates, extracts and deletes.
- Drag pages to move them: a line shows where they land. Dragging a selected page moves the whole selection. On a phone, hold the page first, so a swipe still scrolls.
- Drop a PDF between two pages to insert its pages there, instead of opening it as a tab.
- The keyboard: the arrows go through the pages,
Shiftextends the selection,Spaceadds a page or takes it out,Alt+arrows move the selection one place,Deletedeletes, andCtrl/Cmd+Aselects every page.
Every edit is one step of the history, so undo puts the pages back as they were, a deleted page
with its annotations. The buttons are the page:* commands, so disable and
readOnly turn editing off and leave a plain list of pages. A user without
doc.pages.assemble in their scope sees the plain list too.
The attachments#
The attachments panel lists the files the PDF carries: its own first, then the files pinned to its pages, each with its name, size and description, and its page for a page’s file. A name never shows the characters that can make one file look like another.
- Click a file to save it; click a page’s file to go to it on its page.
- Each file has a menu: its button or a right-click. It saves the file, goes to a page’s file, and deletes the file.
- Attach files with the button in the panel’s bar, beside how many files there are, or drop
them on the panel; a file dropped there is attached, not opened as a tab. A name the document has already gets a number,
notes (2).txt. - A selected file attachment on the page has Save file in its bar.
Saving a file is a download: it goes with documents.download and needs doc.download in the
user’s scope. Attaching and deleting are attachments.adding, which readOnly turns off, and need
doc.attachments.modify; each is one step of the history. From code, viewer.get('attachment')
is the attachment plugin.
A bar in a panel#
The thumbnails, outline and attachments panels each have a bar under their title, named after the
panel: thumbnails, outline and attachments. The outline’s holds
its filter. They’re bars like the toolbar, so the same edits work, and what doesn’t fit goes into a
“More” menu:
layout
.add('acme:print-pages', { to: 'thumbnails' })
.remove(['outline:expand-all', 'outline:collapse-all'], { from: 'outline' });Any panel can have a bar, your own too: its place is { panel: id }.
layout.addBar('review', {
place: { panel: 'comments' },
start: [['acme:approve-all', 'acme:export']],
});A panel’s bars stack under its title in the order you add them. The search panel shows its bars over its list of hits, not in the find bar it becomes when the hits come without their text.
Panels on the same side#
One panel shows per side at a time. Opening another on that side closes the first, and the panel’s header switches between the panels that share its side. Both sides can be open at once: the thumbnails on the left and the comments on the right.
Open from the start#
open: true opens a panel when a document opens, where it docks. Where it would cover the pages,
as a sheet on a phone or over them at medium widths, it waits for its button:
layout.panel('comments', { open: true });A panel people shouldn’t close gets closable: false. Where it docks, it’s always open: it has no
close button, and its button opens it but doesn’t close it. Another panel on its side can take its
place, and when that one closes, it’s back. Over the pages it closes like any other panel, since
it would cover them.
layout.panel('comments', { closable: false });Of two panels on one side that open from the start, the first in the layout opens.
Remove a panel#
layout.removePanel('attachments');Its button goes with it, from every bar and menu, and so do the bars at its top.
Open a panel from code#
Run its command:
viewer.commands.execute('panel:comments');To open one with the cursor in it, ask the shell for focus:
viewer.get('shell').open('search', { focus: true });Your own panel#
Add it with addPanel(id, panel) and give the viewer what goes inside:
layout.addPanel('notes', { side: 'end', title: 'Notes', icon: 'book' });| Field | What it is | Default |
|---|---|---|
side | 'start' or 'end' | 'end' |
title | Its title, in its header and on its button; or titleKey for a translated one | none |
icon | Its button’s icon | none |
present | 'dock', 'overlay' or 'sheet', or one per size: { compact, medium, expanded } | { compact: 'sheet', medium: 'overlay', expanded: 'dock' } |
width | Where it starts, in px, or { initial, min, max } | { initial: 288, min: 200, max: 640 } |
resizable | Whether people can drag its edge | true |
open | Whether it opens when a document opens, where it docks | false |
closable | Whether people can close it where it docks | true |
The viewer draws the frame, the header and the way it opens; you draw what’s inside. See Your own components for the content in each framework.
This one holds a box for notes. In the frame below the viewer is medium width, so the panel opens over the pages, and its edge drags there too:
Loading live preview…
import { Component, ViewEncapsulation } from '@angular/core';
import { EpdfPanel, EpdfPdfViewer, type ViewerConfig } from '@embedpdf/viewer-angular';
const welcome = '/pdfs/welcome.pdf';
@Component({
selector: 'demo-root',
imports: [EpdfPdfViewer, EpdfPanel],
styleUrl: './notes.css',
encapsulation: ViewEncapsulation.None,
template: `
<epdf-pdf-viewer [src]="welcome" [config]="config" style="height: 100%">
<ng-template epdfPanel="notes">
<textarea class="notes" placeholder="Your notes"></textarea>
</ng-template>
</epdf-pdf-viewer>
`,
})
export class App {
readonly welcome = welcome;
readonly config: ViewerConfig = {
layout: (layout) =>
layout
.addPanel('notes', {
side: 'end',
title: 'Notes',
icon: 'book',
width: { initial: 320, min: 240, max: 480 },
})
.add('panel:notes', { to: 'main', section: 'end' }),
};
}Try it: open Notes, the book at the end of the toolbar, and write a line about the cover. Then drag the panel’s left edge to make it wider or narrower, or focus the edge and use the arrow keys.
Good to know#
Each document keeps its panels#
With several documents open, each one remembers which panels were open, so switching tabs brings them back. Widths are the viewer’s: a panel keeps its width from one document to the next.
Focus#
An overlay or a sheet takes focus when it opens and gives it back to its button when it closes;
Escape closes it. A sheet keeps Tab inside it; from an overlay, Tab moves on to the
toolbars, which keep working while it’s open. A docked panel takes focus only when it’s asked
to: it’s part of the page, and Tab reaches it in order.
Search asks, from its button and from Ctrl/Cmd+F (search:find): the cursor goes in its
field, with its text selected, so typing replaces it. Ctrl/Cmd+F works from any text field in
the viewer, the search field among them, and keeps search open. Your code asks with
viewer.get('shell').open(id, { focus: true }); a plain open(id) leaves the cursor where it is.
Your feedback goes directly to the documentation team.