LintPDF LoupePDF

Getting started


title: "LoupePDFViewer (one-line viewer)" description: "Default high-level composition. One JSX line gets you a working multi-page PDF viewer with auto-discovered pages, layers, zoom, color picker, and measure tool." group: "Getting started" order: 3

LoupePDFViewer

The default composition. Drop it into any React 19 project and you have a working PDF viewer — no host-context wiring, no per-page mounting, no worrying about pdf.js workers.

import { LoupePDFViewer } from "@printwithsynergy/loupe-pdf";

export function MyViewer() {
  return <LoupePDFViewer pdfUrl="https://example.com/file.pdf" />;
}

What it does

  • Builds a pdf.js fallback adapter from pdfUrl and configures the worker URL.
  • Wraps ViewerHostContext and (optionally) ViewerServicesContext.
  • Auto-discovers page count, page dimensions, and OCG layers from the PDF.
  • Renders every page in a scrollable, lazy-loaded virtual list (or one at a time with mode="single").
  • Seeds initial layer state from the PDF's defaults (default-on layers enabled, default-off off).
  • Ships a responsive default toolbar with zoom, layers, color picker, and measure tool.
  • Reflows to a bottom-drawer layout under 768 px wide.

Props

PropTypeDefaultNotes
pdfUrlstringrequiredPDF URL fetched by the user's browser. Sign / scope upstream — see Security.
workerSrcstringdefaultPdfWorkerSrc (unpkg, pinned to bundled pdfjs-dist version)Override to self-host the pdf.js worker.
servicesViewerServices(unset)Pass when your host has a backend with separations / densitometer / TAC / annotations / reports. Components for unwired services hide silently.
tokensThemeTokensdefaultThemeTokensBrand palette.
classNamestring""Class hook on the outer shell.
mode"scroll" | "single""scroll"Page rendering mode.
toolsReadonlyArray<"zoom"|"layers"|"color-picker"|"measure">all fourToolbar contents. Pass [] for no toolbar.
initialZoomnumber100Starting zoom percent.
brandstring(none)Optional brand label rendered in the top-left of the toolbar.
header(state: LoupePDFViewerState) => ReactNode(built-in toolbar)Replace the default toolbar. Receives viewer state.
sidebar(state: LoupePDFViewerState) => ReactNode(none)Inject a sidebar next to the page list.
footerReactNode(none)Static footer content rendered below the viewer stage.

What gets hidden when no services are wired

The composition relies on the host-agnostic capability-detection contract from fallback.md. With only pdfUrl set:

ComponentVisibleNotes
Page rendering, navigation, zoompdf.js fallback.
Layer panel✅ if PDF has OCGspdf.js fallback.
Color pickerRGB only (no TAC).
Measure toolPage dimensions from PDF.
Separations❌ hiddenNeeds a backend; pdf.js renders RGB only.
Densitometer❌ hiddenSame.
TAC heatmap❌ hiddenSame.
Annotations❌ hiddenNeeds persistence.
Report links❌ hiddenNeeds a backend.

Pass services to wire the backend-dependent ones. The reference server in server/ is a turnkey option — see server.md.

Slot props

Slot props let you replace individual regions without reimplementing the entire viewer. Each slot accepts a static ReactNode or a render function that receives the current LoupePDFViewerState:

import { LoupePDFViewer } from "@printwithsynergy/loupe-pdf/components";

<LoupePDFViewer
  pdfUrl={url}
  header={(state) => (
    <div>
      <span>Page {state.currentPage}</span>
      <button onClick={() => state.setZoom(state.zoom + 25)}>Zoom in</button>
    </div>
  )}
  footer={<p>Powered by LoupePDF</p>}
/>

LoupePDFViewerState exposes: zoom, setZoom, currentPage, setCurrentPage, pageCount, activeTool, setActiveTool, enabledLayers, toggleLayer, setAllLayers, hasLayers, layersOpen, setLayersOpen.

Custom layouts

<LoupePDFViewer> is purely additive. The lower-level surface stays exactly as before — for bespoke layouts compose PageCanvas, LayerPanel, MeasureTool, ColorPickerTool, etc. with your own context providers. See components.md for the per- component reference.

Other integration tiers

  • Less boilerplate<LoupePDFDemo> bakes in upload, URL paste, drag-drop, and validation. ~5 lines.
  • More controluseLoupePDF() + <LoupePDFProvider> manage state while you build the layout.
  • Full custom — wire ViewerHostContext + ViewerServicesContext yourself. See architecture.md.

Security

The pdfUrl you pass is fetched verbatim by the user's browser. If a user shouldn't be able to read that PDF, the host must enforce that upstream — sign the URL, scope it, expire it. The viewer is a pure renderer and doesn't authenticate anything.

The pdf.js worker is loaded from unpkg by default. For deployments that can't reach unpkg (intranet, air-gapped CI, strict CSPs), set workerSrc to a self-hosted URL or import the worker into your app's build and pass its bundler-resolved URL.