GitHub

PDF Viewer

A pdfjs-backed PDF viewer with zoom, rotate, fit-to-width, download, and a per-page overlay slot for bounding-box citations.

The PDF Viewer renders a document with pdf.js directly — no react-pdf wrapper. It does continuous-scroll rendering, zoom, rotate, fit-to-width, and download, and exposes a per-page overlay slot so you can draw bounding-box citations (edit fields, extraction sources) on top of the rendered page.

It is built without useEffect: document and page loading use React's use() with Suspense, and each page renders into its canvas from a ref callback (with a cleanup that cancels the in-flight render).

Installation

pnpm dlx shadcn@latest add @retab/pdf-viewer

This pulls in pdfjs-dist. The worker is resolved from the installed package by your bundler (Vite, webpack, or Turbopack) — there is no runtime CDN call.

Usage

import { PdfViewer } from "@/components/ui/pdf-viewer"
 
export function Example() {
  return <PdfViewer src="/document.pdf" className="h-[600px]" />
}

Bounding-box overlays

renderPageOverlay runs per page and receives the rendered page size, so normalized boxes map with simple percentages:

<PdfViewer
  src={url}
  renderPageOverlay={({ pageNumber }) =>
    fieldsOnPage(pageNumber).map((f) => (
      <div
        key={f.key}
        className="absolute outline outline-2 outline-indigo-500"
        style={{
          left: `${f.bbox.left * 100}%`,
          top: `${f.bbox.top * 100}%`,
          width: `${f.bbox.width * 100}%`,
          height: `${f.bbox.height * 100}%`,
        }}
      />
    ))
  }
/>

Props

PropTypeDescription
srcstringURL of the PDF (same-origin or CORS-enabled).
scalenumberOptional fixed scale; omit to fit page width to the container.
toolbarbooleanShow the zoom/rotate/download toolbar. Defaults to true.
downloadFileNamestringFilename for the download button.
renderPageOverlay(props) => ReactNodePer-page overlay; receives { pageNumber, width, height, scale, rotation }.
classNamestringOptional class on the viewer shell.