EmbedPDF

Theming

Give the viewer your brand color, and every part follows it: pressed buttons, the active tab, focus rings, the text selection, the handles of a selected annotation.

{
  theme: { preference: 'system', accent: '#7c3aed' },
}

preference is 'light', 'dark' or 'system', which follows the user’s setting and switches when they change it. theme: 'dark' is short for theme: { preference: 'dark' }. theme follows its prop, so a theme switch in your app switches the viewer too.

Every color#

Change any of them with tokens for both light and dark, with light for light only, and with dark for dark only:

{
  theme: {
    accent: '#0f6e56',
    tokens: { radius: '2px' },
    light: { surface: '#fbfaf7' },
    dark: { surface: '#16201c', 'page-shadow': 'none' },
  },
}

When more than one sets a token, light or dark wins in its mode, then tokens, then accent. A CSS variable wins over all of them: see From CSS.

This one gives each color its light value in light and its dark value in dark, and a switch in the app sets preference:

Loading live preview…

import { useState } from 'react';
import { PDFViewer, type ThemeConfig } from '@embedpdf/viewer-react';

import './colors.css';

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

// What both modes share, then the colors of each.
const brand: ThemeConfig = {
  accent: '#0f6e56',
  tokens: { radius: '2px' },
  light: { surface: '#fbfaf7', canvas: '#ece8df' },
  dark: {
    accent: '#4fbf98',
    surface: '#16201c',
    'surface-raised': '#1d2a25',
    canvas: '#0c1210',
    hover: '#24332d',
  },
};

export default function App() {
  const [dark, setDark] = useState(false);
  return (
    <div className={dark ? 'app dark' : 'app'}>
      <header className="bar">
        <label>
          <input
            type="checkbox"
            checked={dark}
            onChange={(event) => setDark(event.target.checked)}
          />
          Dark
        </label>
      </header>
      <PDFViewer
        className="viewer"
        src={welcome}
        theme={{ ...brand, preference: dark ? 'dark' : 'light' }}
      />
    </div>
  );
}

Try it: tick Dark above the viewer, then open the menu at the top left: the bars, the menus and the space behind the pages all take your dark colors. Untick it to go back.

TokenWhat it colorsLightDark
accentPressed buttons, primary buttons, the active tab, links, focus rings, and every part on the pages that has no color of its own#3b82f6#60a5fa
backgroundBehind the bars and panels#f3f4f6#111827
surfaceBars and panels#ffffff#1f2937
surface-raisedMenus, sheets, dialogs and floating bars#ffffff#1f2937
textLabels and text#111827#f9fafb
text-mutedSecondary text, such as a page count#6b7280#9ca3af
borderLines between bars, panels and groups#d1d5db#4b5563
hoverA button under the pointer#f3f4f6#374151
canvasBehind the pages#f3f4f6#0d1117
page-backgroundA page before its picture arrives#ffffff#ffffff
page-shadowThe shadow under each page; none for none0 6px 18px rgb(0 0 0 / 0.18)0 6px 18px rgb(0 0 0 / 0.18)
tooltipBehind a tooltip’s wordstext’s colortext’s color
tooltip-textA tooltip’s wordssurface’s colorsurface’s color
radiusThe corners of buttons, menus, panels, tooltips and the bars floating over the pages6px6px
fontEvery word the viewer writes, its tooltips’ tooyour system’s fontyour system’s font

What’s drawn on the pages has its own colors: the text selection, search matches, the handles of a selected annotation, form fields. Every one follows accent until you set it, and every one is listed on Colors & theming in the headless docs, as a plugin setting and a CSS variable. Both work in the viewer.

From CSS#

Every token is also a CSS variable: --epdf- and its name. Set it on the viewer or on any element around it. A CSS variable wins over the config:

embedpdf-viewer {
  --epdf-accent: #e91e63;
  --epdf-radius: 0;
}
 
[data-theme='dark'] embedpdf-viewer {
  --epdf-surface: #101418;
}

Use it to keep colors in your stylesheet, to follow a theme class your app already switches, or to give two viewers on one page their own look.

Restyle a part#

The main parts have a name you can reach with ::part(), for what tokens don’t cover:

embedpdf-viewer::part(toolbar-button) {
  border-radius: 999px;
}
 
embedpdf-viewer::part(mode-tab-active) {
  font-weight: 600;
}
 
embedpdf-viewer::part(toolbar-review) {
  background: #fef3c7;
}
PartWhat it is
toolbar, toolbar-<id>A bar, and the bar with that id: toolbar-main, toolbar-pager, or toolbar-review for a bar you added as review
toolbar-button, toolbar-button-activeA button in a bar, and a pressed one
toolbar-button-primaryA primary button
tooltipA tooltip
mode-tab, mode-tab-activeA mode tab, and the selected one
tab, tab-activeA document tab, and the open one
menu, menu-item, menu-item-activeA menu, a row, and the row of the current choice
panel, panel-headerA panel, and its header
page-loading, page-errorThe spinner on a page whose picture is on its way, and the message on one that couldn’t load

Part names are kept between minor versions, like every other id.

In your own components#

Your own components inherit the viewer’s font and text color, and can use every token as a CSS variable:

.version-picker {
  background: var(--epdf-surface);
  color: var(--epdf-text);
  border: 1px solid var(--epdf-border);
  border-radius: var(--epdf-radius);
}

Good to know#

Your page’s CSS stays out#

The viewer draws inside its own shadow DOM, so your page’s styles don’t change it and its styles don’t change your page. A global button { … } rule, a CSS reset, or html { font-size: 62.5% } leaves the viewer as it is. What does reach it is what you aim at it: the CSS variables above and ::part().

Tailwind#

Set a variable with an arbitrary property on an element around the viewer: class="[--epdf-accent:var(--color-violet-600)]".

Colors saved in the PDF#

An annotation’s color, a redaction’s fill and a form field’s look belong to the document, not to the theme: they look the same in every app that opens the file. Their defaults are the tools’ settings: see Annotations.

Was this page helpful?

Your feedback goes directly to the documentation team.