# PDF Document Viewer

> A PDF reader with a page control that expands into visual navigation, searchable documents and a reviewed HTML reading mode for phones.

Canonical: https://gsapvault.com/ui-elements/pdf-document-viewer
Live demo: https://gsapvault.com/demos/pdf-document-viewer/index.html

| Property | Value |
|----------|-------|
| Type | ui-element |
| Tier | paid |
| Price | £5 |
| Difficulty | advanced |
| Plugins | Flip |
| Techniques | flip-layout, state-transition, keyboard-navigation, focus-management |
| Uses Lenis | No |

## Lighthouse, as measured

Google Lighthouse on the demo, 15 September 2026. A measurement of the demo as shipped, not a promise for your page.

| Category | Score |
|----------|-------|
| Accessibility | 100 |
| Best practices | 100 |

No performance score is published: it depends on the deployment (server compression, caching, CDN, connection and device) rather than on the code, so measure it where it will live.

## Overview

Let readers browse the original PDF, find a page quickly and switch to a reviewed reading version without losing their place. The compact page-position control expands into thumbnails and contents with a connected Flip transition. On phones it becomes a bottom sheet, with page controls kept within reach.

The Original view renders real PDFs with selectable text, links, search and zoom. Reading mode uses an author-supplied HTML companion with page mappings, comfortable text and adjustable size. An original eight-page home energy guide demonstrates both views. Local files stay in the browser, and opening another document clears the previous companion.

## Features

- Original PDF rendering with selectable text, links, fit-width, fit-page and zoom steps
- Page-position control expands into a thumbnail navigator and document contents
- Mobile navigator with focus containment, inert background and focus return
- Incremental search with match highlighting, next/previous and no-text states
- Reviewed HTML reading edition with page mappings and 16–24px text controls
- Left, centre and right alignment with fullscreen quiet reading and zoom restoration
- Download the original PDF or an explicitly matched clean Reading PDF
- Local PDF input, password recovery and a direct original-document route
- Bounded page rendering and thumbnail queues with cancellation and teardown
- Independent instances, documented methods and root-scoped events
- Light and dark UI themes; PDF page colours remain unchanged

## Use Cases

- Reports that need both a faithful PDF layout and comfortable phone reading
- Product guides and handbooks with visual page navigation
- Brochures with a reviewed HTML companion
- Document libraries that render files locally without a hosted conversion service

## Vibe-Code Ready Setup

This UI element includes `START-HERE-AI.md`, a product-specific copy-paste setup prompt for Cursor, Claude Code, ChatGPT, GitHub Copilot, Windsurf, and other coding assistants. It tells the assistant to inspect the existing stack, integrate the supplied files, preserve the design, scope selectors, retain accessibility and responsive behaviour, add framework-appropriate GSAP cleanup, and report what it tested.

[How AI-assisted setup works](https://gsapvault.com/vibe-coding)

## How It Works

PDF.js renders the source document while an author-controlled companion provides the reading edition. Page mappings connect the two views. The position control and navigator share a frame, so GSAP Flip keeps the current-page indicator visually attached as the panel changes size. Rendering and search cancel obsolete work when documents change; each instance owns its own state and cleanup.

## Documentation

How this UI element works and how it goes into a page. The reference you use once you own the files (worked examples, events, the programmatic API, the class list) ships with the download.

### Quick Start

Serve the folder over HTTP or HTTPS. ES modules and PDF workers do not work reliably from a `file://` URL.

**In the head:**

_Code snippet omitted: it ships with the download._

**In the body:**

_Code snippet omitted: it ships with the download._

Apply `data-variant="light"` or `data-variant="dark"` to the body or component root. Keep the original link and companion in the initial markup: they remain available without JavaScript. A `<template>` is also accepted, but a template alone is not a no-JavaScript fallback.

### Using It With Your Own Design

The mount root owns its height. Give it a definite height or a height resolved by your host's layout. Everything the viewer creates is scoped under `.pdfv`; the script does not query demo stage classes. Copy the component assets and retain their relative paths.

Re-value `--ground`, `--ground-2`, `--raised`, `--hover`, `--ink`, `--ink-2`, `--ink-3`, `--line`, `--input-line`, `--accent`, `--accent-text`, `--selected`, `--selected-ink`, `--control-radius`, `--panel-radius` and the two shadow tokens to match your interface. Load Inter yourself, or set `--font` to your own UI font.

The theme toolbar, `.stage`, demonstration mounting code and `__thumbnail` hook in `index.html` are demo-only furniture. The reader and its document controls are the component. Keep `pdf-engine.css`: its page, text and annotation positioning is functional. Do not put a transform or CSS scale on a reader while someone is selecting text.

### Downloading the Reading Edition

Supply `readingPdf` with the URL of the PDF that matches your authored `readingContent`. In Reading view, the toolbar download saves this clean edition; in Original view it saves the source PDF. Document options lists both, including on phones. The reading download appears only when both a companion and its PDF URL are supplied. Invalid or unsupported URL schemes are ignored. Use a same-origin URL for a reliable browser download; cross-origin servers control whether their response downloads or opens.

The supplied `home-energy-guide-reading.pdf` contains the full clean Reading edition with selectable text, headings, contents links, the diagram and table. It has no viewer controls or search highlights and is independent of the current theme, zoom and quiet mode. The accompanying standalone HTML is the print source, with its styles and illustration embedded.

To regenerate the sample from `assets/reading-companion.html`, run `node source/generate-reading-download.mjs` with `@playwright/test` and Playwright Chromium installed. The PDF is generated ahead of time from the reviewed companion; the viewer does not automatically convert arbitrary uploaded PDFs or generate a new PDF in the reader’s browser.

Opening another document clears the previous reading PDF along with its companion. Pass an explicitly matched `readingPdf` and `readingContent` in the replacement options to enable the new document’s download.

### Alignment and Quiet Mode

Choose Left, Centre or Right in Document options, or call `reader.setAlignment("left")`. This moves the PDF pages or the reading article inside the reader, while keeping text left aligned. Fit page and Reading views usually leave room for the alignment to show; Fit width fills the available space. The viewer itself fits its host container, so place that container with your own layout CSS.

Quiet mode expands the reader to fill the browser viewport, with the surrounding page and reader controls out of sight. The PDF grows to a readable width of up to 900px, filling the available width on phones; Reading mode uses at least 20px text in a column up to 760px wide. A 280ms Flip transition connects the embedded and expanded layouts. Use Exit quiet mode or Escape to return. Your previous zoom preference is restored, and your current reading position follows you back, including pages visited while quiet. There is no idle timer.

A native modal dialog places the reader above host clipping and transforms while retaining inherited theme tokens. Keyboard focus enters the document so PageDown works immediately. The dialog contains focus and makes the surrounding page inert; host scroll and Lenis state are restored on exit or teardown. Only one reader owns quiet mode at a time. Opening any reader’s page navigator exits it. The document, links and error/password recovery remain available, while automatic search jumps pause. Within an iframe, quiet mode fills that frame’s viewport.

Use the toolbar's Quiet mode button (Document options on phones), `reader.setQuiet(true)`, or the `quiet: true` mount option. Alignment and quiet mode remain selected when opening another PDF. The demo strip exposes both choices for previewing them. Its desktop preview starts in Fit page so alignment is immediately visible; the component defaults to Fit width.

### What's Included

- `assets/script.js`: editable component source.
- `assets/pdf-engine.js` and `pdf-engine.css`: PDF.js rendering, text selection, links and bounded rendering queues.
- `assets/vendor/pdfjs/`: pinned PDF.js display module, matching worker, character maps, standard fonts, WASM and licence notices.
- `assets/vendor/pdfjs/dependency-lock.json`: dependency pin and package integrity; `assets/vendor/pdfjs/README.md`: hosting and licence notes.
- `assets/home-energy-guide.pdf`: eight original pages with an outline, links, a two-column layout, diagram and table.
- `assets/home-energy-guide-reading.pdf` and `home-energy-guide-reading.html`: clean reading downloads and self-contained print source.
- `source/generate-reading-download.mjs`: reproducible clean HTML/PDF generation.
- `assets/reading-companion.html`: reviewed semantic HTML; its content is embedded in the demo for the no-JavaScript fallback.
- `assets/energy-diagram.svg`, `assets/provenance.md` and `source/generate-guide.py`: original illustration, provenance and reproducible guide source.
- `thumbnail.html`: deterministic catalogue capture input, excluded from buyer downloads.

### Reading Companion Contract

Pass an author-controlled `Element`, `DocumentFragment` or `HTMLTemplateElement` as `readingContent`. The viewer clones it; it never fetches or converts arbitrary HTML. Treat this as trusted application content, with the same review as your page templates. Never pass untrusted markup from an upload or remote document.

Each mapped section needs a stable `id` and a one-based `data-pdf-page`. Multiple sections may map to the same page. Give paragraphs stable IDs too. Own the heading hierarchy, reading order, alt text, figure captions, table captions and header associations. The complete eight-page example shows the contract.

The viewer prefixes cloned IDs and their internal references per instance. Inactive modes are hidden from the accessibility tree. Switching views selects the nearest mapped section/page and announces that the correspondence is approximate. Within a reading session, text resizing preserves a visible text anchor; returning to the same mapped page restores the previous reading anchor. It does not claim exact PDF paragraph coordinates.

Opening another PDF clears the previous companion. To provide an explicitly matched replacement:

_Code snippet omitted: it ships with the download._

Without a companion, only Original is offered and Document options explains that no reading version is available. Scanned PDFs report “No searchable text”; OCR and automatic conversion are outside this component. A PDF viewer cannot repair an arbitrary source document's reading order or make it WCAG compliant.

### Methods and Events

| Method                                                | Behaviour                                                                                                                                                                                    |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PDFViewer.mount(root, options)`                      | Returns an independent reader; mounting an already mounted root returns its existing instance.                                                                                               |
| `await reader.open(source, replacements?)`            | Opens a URL or `File`; latest request wins. Clears prior companion/search. Resolves current state after success or a displayed error; superseded work resolves without a result.             |
| `reader.goToPage(number)`                             | Jumps to a valid integer page and announces it; returns false if unavailable or out of range.                                                                                                |
| `reader.setMode('original' \| 'reading')`             | Returns false when Reading is unavailable; otherwise maps position.                                                                                                                          |
| `reader.setZoom('fit-width' \| 'fit-page' \| number)` | In Original, numeric scale is 0.5–3. In Reading, number means text pixels, clamped to 16–24.                                                                                                 |
| `reader.setAlignment('left' \| 'center' \| 'right')`  | Aligns document pages or the reading article within the reader; returns false for invalid values.                                                                                            |
| `reader.setQuiet(boolean)`                            | Expands to focused reading or restores the embedded layout and zoom, preserving your current place. Returns false for invalid values.                                                        |
| `reader.openNavigator()` / `closeNavigator()`         | Opens or closes page navigation.                                                                                                                                                             |
| `reader.getState()`                                   | Title, page, numPages, mode, zoom, alignment, quiet, ready, loading, readingAvailable, readingPdfAvailable, navigatorOpen and error. After destroy, returns `{destroyed:true, ready:false}`. |
| `reader.destroy()` / `reader.revert()`                | Cancels work, releases resources and restores the root's original nodes and class. Remount to reuse.                                                                                         |

Mount options are `source`, `title`, `readingContent`, optional `readingPdf`, `initialPage` (1), `initialMode` (`original`), `alignment` (`center`) and `quiet` (`false`). `open()` replacements accept `title`, `readingContent` and `readingPdf`. Initial settings apply only to the initial document.

Events fire on the mount root, with payload in `event.detail`:

| Event                       | Payload                                                                                        |
| --------------------------- | ---------------------------------------------------------------------------------------------- |
| `pdfviewer:ready`           | `{numPages, title, readingAvailable}`                                                          |
| `pdfviewer:pagechange`      | `{page, numPages, mode, explicit}`; native scrolling updates state without live announcements. |
| `pdfviewer:modechange`      | `{mode, page, approximate:true}`                                                               |
| `pdfviewer:alignmentchange` | `{alignment}`                                                                                  |
| `pdfviewer:quietchange`     | `{quiet}`                                                                                      |
| `pdfviewer:error`           | `{name, message}`                                                                              |

### Keyboard & Accessibility

Tab follows the toolbar, any open search controls, active document and bottom controls. Enter/Space activate buttons. No global arrow-key or find shortcut is captured, and browser pinch zoom remains available.

| Context / key                       | Behaviour                                                                           |
| ----------------------------------- | ----------------------------------------------------------------------------------- |
| Quiet mode / Escape                 | Returns to the embedded reader and focuses Quiet mode (Document options on phones). |
| Navigator / Escape                  | Closes and returns focus to the page-position button.                               |
| Navigator tabs / arrows, Home, End  | Selects adjacent, first or last tab.                                                |
| Page field / Enter                  | Validates the document range and jumps.                                             |
| Native page range / arrows          | Previews and commits a page using native keyboard behaviour.                        |
| Search / Enter or Shift+Enter       | Next or previous result.                                                            |
| Search or Document options / Escape | Closes the surface and restores its trigger's focus.                                |
| Mobile navigator / Tab or Shift+Tab | Keeps focus inside the labelled modal dialog.                                       |

Desktop navigation is an anchored dialog without a focus trap. Outside interaction closes it. On phones it is a modal bottom sheet: the rest of the page is inert, scrolling is locked, and prior state returns on close. Opening another reader's mobile modal releases the previous reader's lock first. A polite status region announces ready, explicit jumps, errors and mode changes, without rereading the whole document on every scroll.

Reading headings, tables and images stay semantic. Internal reading links move focus to their destination. PDF text/link accessibility still depends on source content; keyboard navigation and a reviewed companion do not replace a source document accessibility review. Perform a manual screen-reader pass with your target browsers and document before release.

With reduced motion, navigation and quiet mode change state immediately. With GSAP blocked, all navigation remains functional. With PDF.js or its worker blocked, the source link and available HTML edition still work. Without JavaScript, initial author-supplied content remains readable.

### How It Works

The page-position control and expanded navigator share a frame. GSAP Flip records their layout and animates the width, height and position over 220ms. Page content is never curled or animated in scale.

PDF.js supplies page pixels, text layers, links, outlines and search text. Only visible pages and a small buffer are rendered, with at most five full-size canvases, two concurrent page renders, a four-megapixel cap and a 4096px dimension cap. The thumbnail queue is separate: two renders at a time and an 80-image cache. Search yields between pages and within dense results, and obsolete jobs are cancelled on replacement. A cancelled search cannot overwrite newer UI state.

### Themes

Light is the default, with neutral grey surroundings, raised controls and electric-blue selection. Dark uses the same tokens and layout. PDF pages retain their original colours; the reader never inverts page images. The HTML edition follows the selected theme. `?variant=dark` is a demo convenience.

### Requirements and Hosting

- Modern browsers supporting ES modules, module workers, native modal dialogs, ResizeObserver, IntersectionObserver, `inert` and PDF.js 5.4.624. Verify your supported browser versions against PDF.js's browser requirements.
- GSAP 3.12+ with Flip for the transition. The demo uses the centrally pinned GSAP 3.15.0. Both are optional for operation.
- Self-host all `assets/vendor/pdfjs/` files. Display module and worker must have identical versions. See `assets/vendor/pdfjs/README.md` before updating.
- Serve `.js` / `.mjs` as JavaScript, `.wasm` as `application/wasm`, and `.pdf` as `application/pdf`.
- Remote PDFs need the source server's CORS permission. No server proxy is included. Same-origin files are simplest. PDF requests do not send cross-origin credentials.
- Allow your self-hosted scripts, worker and fonts in your CSP. The demo's optional GSAP and Google Fonts URLs need their respective allowlists; self-host those too if your policy requires it. PDF.js may use blob URLs for internal resources.
- Embedded PDF scripts and XFA are not enabled. The viewer exposes PDF links but does not implement form editing, annotation authoring or attachments.
- Local `File` data stays in the browser. Owned object URLs, workers, render jobs, listeners, observers and GSAP contexts are released by teardown.

## What You Get

- `index.html`: working demo page
- `assets/script.js`: commented, readable source
- `assets/style.css`: effect styles
- `README.md`: full documentation with examples and framework integration notes
- `START-HERE-AI.md`: product-specific copy-paste prompt for AI-assisted setup
- `LICENSE.txt`: standard license terms
- Lifetime updates: re-download anytime from your library

## Get the Code

This is a premium UI element. The standard license costs £5 one-time and covers unlimited personal and commercial projects with no attribution required for our code. Bundled third-party assets retain their own licences and attribution requirements. The only restrictions: no redistribution of the code itself and no competing effect libraries.

- [Buy PDF Document Viewer](https://gsapvault.com/ui-elements/pdf-document-viewer)
- [The Vault (£99 one-time, best value): every collection in the Vault library, plus future items added to those collections](https://gsapvault.com/effects)

---

From [GSAP Vault](https://gsapvault.com): production-ready GSAP animation effects. Full catalog for agents: https://gsapvault.com/llms-full.txt
