DEV Community

korial29
korial29

Posted on

A PDF Viewer Web Component That Doesn't Make You Choose Between Themeable and Lightweight

Drop a PDF into a web page and you usually end up picking one of two uncomfortable options: embed PDF.js's own viewer in an <iframe> (works, but you're stuck behind an iframe boundary — no theming, no styling it to match your app), or pull in pdfjs-dist directly and build your own toolbar, zoom controls, search, and page navigation from PDF.js's fairly low-level rendering API.

lit-pdf-viewer is a native web component that skips both compromises: a real custom element (not an iframe), with a full toolbar already built, that you can theme like any other element on your page.

Why a custom element, specifically

<lit-pdf-viewer> is a Web Component — no React/Vue/Angular adapter needed. Because custom elements are a browser primitive rather than a framework integration, it behaves identically everywhere:

<script type="module">
  import 'lit-pdf-viewer';
</script>

<lit-pdf-viewer src="path/to/document.pdf"></lit-pdf-viewer>
Enter fullscreen mode Exit fullscreen mode

Or inside a Lit component, same element, same API:

import 'lit-pdf-viewer';
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';

@customElement('my-app')
class MyApp extends LitElement {
  render() {
    return html`<lit-pdf-viewer src="path/to/document.pdf"></lit-pdf-viewer>`;
  }
}
Enter fullscreen mode Exit fullscreen mode

It fills the height of its host, so give it one:

lit-pdf-viewer {
  display: block;
  height: 100vh;
}
Enter fullscreen mode Exit fullscreen mode

The bundle cost, honestly

Adding the component costs about 17 KB gzipped — that's the whole thing, Lit runtime included, before you've loaded a single PDF. PDF.js itself (~196 KB gzipped) is only pulled in via a dynamic import() once a <lit-pdf-viewer> actually connects to the page or a print is triggered — not just for having the package listed in your dependencies.

What's in the toolbar

Not a bare canvas — the toolbar, search, and page controls ship built in:

  • Zoom: page-fit, page-width, actual size, or a custom numeric level
  • Page navigation: previous/next, first/last, direct page input
  • Text search, including multi-phrase search and a whole-word mode
  • Rotate clockwise / counter-clockwise
  • Text selection and hand-tool modes, full-screen, download, print (with an optional separate higher-quality print source)
  • Keyboard accessibility
const terms = ['invoice total', 'due date'];

html`
  <lit-pdf-viewer src="invoice.pdf" .searchQueries=${terms}></lit-pdf-viewer>
`;
Enter fullscreen mode Exit fullscreen mode
<!-- Serve a screen-optimized PDF but print a higher-quality one -->
<lit-pdf-viewer src="document-screen.pdf" printSrc="document-print.pdf"></lit-pdf-viewer>
Enter fullscreen mode Exit fullscreen mode

Theming through the shadow boundary

Every visual surface — toolbar background, buttons, icons, the search bar, tooltips, the error banner — is driven by CSS custom properties with sensible defaults. Set them on the element itself, or any ancestor (:root included), and they inherit down through every nested shadow root automatically — no ::part() gymnastics required for the common case:

lit-pdf-viewer {
  --litpdf-toolbar-background: #1e1e1e;
  --litpdf-icon-color: #eee;
  --litpdf-accent-color: #ff6b35;
  --litpdf-surface-background: #2a2a2a;
  --litpdf-text-color: #eee;
}
Enter fullscreen mode Exit fullscreen mode

i18n out of the box

Toolbar, search bar, error panel, and loading indicator are translated in English, French, Spanish, and German, auto-detected from navigator.language with English as the fallback:

<lit-pdf-viewer src="doc.pdf" locale="es"></lit-pdf-viewer>
Enter fullscreen mode Exit fullscreen mode

Override individual strings (or add a language of your own) without losing the rest of the resolved locale:

html`
  <lit-pdf-viewer
    src="doc.pdf"
    locale="en"
    .translations=${{
      toolbar: { print: 'Print document' },
      search: { placeholder: 'Find in this document...' },
    }}
  ></lit-pdf-viewer>
`;
Enter fullscreen mode Exit fullscreen mode

Where this sits relative to the alternatives

A few other PDF-in-the-browser web components already exist, and they're worth knowing about: pdfjs-viewer-element wraps PDF.js's own default viewer inside an isolated <iframe> — a solid drop-in choice if you don't need it to look like the rest of your app. pdf-viewer-element (from Lit's own creator) stays intentionally close to the metal — a rendering primitive rather than a full toolbar. lit-pdf-viewer targets the space between the two: a real, styleable custom element with the toolbar, search, and i18n already built, for the common case of "I want a PDF viewer in my page that looks like it belongs there."

Install

npm install lit-pdf-viewer
npx lit-pdf-viewer-install <your-public-dir>
Enter fullscreen mode Exit fullscreen mode

The second command copies the PDF.js worker, CMaps, and icon font into your public directory — required static assets the component serves alongside your app.

Apache-2.0 licensed. Issues and PRs welcome.

Top comments (0)