Skip to main content

PdfViewer

A React component for rendering PDF documents with text selection, custom annotations, sidebar navigation, and in-document search.

Import

import {
BasePdfViewer,
PdfViewer,
} from "@osdk/react-components/experimental/pdf-viewer";
  • PdfViewer — Primary component for OSDK usage. Accepts an OSDK Media object, handles fetching the PDF contents, and renders the viewer.
  • BasePdfViewer — Lower-level component that accepts a URL string or ArrayBuffer directly. Use this when you already have the PDF source.

Usage

With OSDK Media

import { PdfViewer } from "@osdk/react-components/experimental/pdf-viewer";

<PdfViewer media={employee.employeeDocuments} />;

With a URL or ArrayBuffer

import { BasePdfViewer } from "@osdk/react-components/experimental/pdf-viewer";

// From a URL
<BasePdfViewer src="https://example.com/document.pdf" />

// From an ArrayBuffer (e.g. file input or fetch)
<BasePdfViewer src={arrayBuffer} />

With annotations and sidebar

<PdfViewer
media={myMedia}
initialPage={3}
initialScale={1.5}
initialSidebarOpen
sidebarMode="outline"
enableDownload
annotations={{
1: [
{
id: "finding-1",
type: "highlight",
page: 1,
rect: { x: 72, y: 500, width: 200, height: 16 },
label: "Key finding",
color: "rgba(255, 200, 0, 0.4)",
},
],
}}
onAnnotationClick={(annotation) => console.log("Clicked:", annotation.id)}
/>;

Props

PdfViewerMediaProps

PropTypeRequiredDescription
mediaMediaYesOSDK Media object to fetch PDF contents from

Plus all props from PdfViewerProps except src.

PdfViewerProps (BasePdfViewer)

PropTypeDefaultDescription
srcstring | ArrayBufferPDF source URL or binary data (required)
annotationsRecord<number, PdfAnnotation[]>{}Annotations keyed by page number (1-indexed)
onAnnotationClick(annotation: PdfAnnotation) => voidCallback when an annotation is clicked
initialPagenumber1Page to display on first render
initialScalenumber1.0Initial zoom level
initialSidebarOpenbooleanfalseWhether the sidebar is initially open
sidebarModeSidebarMode"thumbnails"Which sidebar panel to show: "thumbnails" or "outline"
outlineIconsPartial<Record<number, React.ComponentType>>Custom icon components for each outline depth level (0-indexed)
enableDownloadbooleanfalseWhether the download button is shown in the toolbar
classNamestringCSS class applied to the root element

Features

Text selection and copy

The component renders an invisible text layer on top of each PDF page using the pdf.js TextLayer API. This enables native browser text selection — users can click and drag to select text, then copy it with Ctrl+C / Cmd+C.

The sidebar supports two modes controlled by the sidebarMode prop:

  • Thumbnails ("thumbnails") — Renders page thumbnails. Click a thumbnail to jump to that page.
  • Outline ("outline") — Shows the document's table of contents. If the PDF has embedded bookmarks, those are displayed with their original nesting and bold/italic styling. If no bookmarks exist, headings are automatically extracted from the text content based on font size.

Annotations

Annotations are positioned using PDF coordinate space (origin at bottom-left of the page). The component transforms these to CSS positioning automatically. Four annotation types are supported:

TypeVisual
highlightSemi-transparent colored rectangle
underlineColored bottom border
commentSmall circular marker
pinRotated teardrop marker

Each annotation accepts an optional color CSS value to override the default theme color, and a label string shown as a tooltip on hover. Annotations are keyboard-accessible (focusable and activatable with Enter/Space).

Search (Ctrl+F / Cmd+F)

Pressing Ctrl+F (or Cmd+F on macOS) opens a search bar that finds text across all pages. Matches are highlighted in the text layer, and the current match is visually distinguished. Navigation between matches:

  • Enter — next match
  • Shift+Enter — previous match
  • Escape — close search bar

The search bar shows a match counter (e.g. "3 of 12"). The toolbar includes a search button as an alternative to the keyboard shortcut.

Toolbar

The built-in toolbar provides:

  • Page navigation — previous/next buttons and a page number input
  • Zoom controls — zoom in/out in 25% steps (range: 25%–500%)
  • Rotate controls — rotate all pages left (counter-clockwise) or right (clockwise) in 90° increments
  • Sidebar toggle — open/close the sidebar panel
  • Search button — opens the search bar
  • Download button — downloads the PDF (hidden by default, enable with enableDownload)

Lazy page rendering

Pages are rendered lazily by the pdf.js PDFViewer — only pages in or near the viewport are mounted. This keeps memory usage and render cost low for large documents.

High-DPI rendering

The canvas renderer accounts for window.devicePixelRatio to produce sharp text on Retina and other high-DPI displays.

Architecture: three tiers

The PDF viewer is designed as a three-tier API so you can choose the right level of abstraction:

  1. Components (PdfViewer / BasePdfViewer) — Drop-in viewers with full UI out of the box.
  2. Building block components — Assemble a custom layout using PdfViewerContent, PdfViewerToolbar, etc.
  3. Hooks — Build entirely custom components. The hooks do all the heavy lifting.
Tier 1: PdfViewer / BasePdfViewer          ← full viewer, zero assembly
Tier 2: Building block components ← custom layout, standard parts
Tier 3: usePdfViewerState / usePdfViewerCore ← custom everything
└── Primitive hooks ← maximum control

Building blocks

All building blocks are exported from @osdk/react-components/experimental/pdf-viewer for composing custom PDF viewer layouts. Use PdfViewerContent as the foundation and add whichever chrome you need.

ComponentDescription
PdfViewerContentCore PDF renderer (scroll area + annotations). No toolbar/sidebar.
PdfViewerToolbarPage navigation, zoom, rotation, search, and download controls
PdfViewerSearchBarSearch input with match count and prev/next navigation
PdfViewerSidebarThumbnail sidebar showing page previews
PdfViewerOutlineSidebarDocument outline sidebar (bookmarks or extracted headings)
PdfViewerAnnotationLayerAnnotation overlay for a single PDF page

Example: content-only viewer

import { PdfViewerContent } from "@osdk/react-components/experimental/pdf-viewer";

<PdfViewerContent
src="https://example.com/document.pdf"
initialScale={1.5}
onPageChange={(page) => console.log("Page:", page)}
onScaleChange={(scale) => console.log("Scale:", scale)}
/>;

Hooks

The hooks are the backbone of the PDF viewer. They handle all state management, pdfjs initialization, event synchronization, and feature logic — so you can build custom components without re-implementing any of it.

Composition hooks

These compose the primitive hooks and manage all UI state for you.

HookDescription
usePdfViewerStateFull-featured hook. Includes everything from usePdfViewerCore plus zoom helpers, rotation, sidebar, search, outline, download, and keyboard shortcuts.
usePdfViewerCoreMinimal hook to render and navigate a PDF. Handles document loading, pdfjs viewer init, scale/page sync, and annotation portals.

Primitive hooks

For maximum control, use the primitive hooks directly.

HookDescription
usePdfDocumentLoads a PDFDocumentProxy from a URL or ArrayBuffer using pdf.js. Manages the web worker lifecycle.
usePdfViewerInitializes the pdfjs PDFViewer, EventBus, PDFLinkService, and PDFFindController.
usePdfViewerSyncBidirectional sync between React state and the pdfjs viewer (scale + page). Returns scrollToPage.
usePdfViewerSearchFull-text search across the PDF. Manages query, match count, navigation, and the search open/close state.
usePdfAnnotationPortalsListens for page render events and provides portal targets for overlaying React annotation components on each page.
usePdfOutlineExtracts outline items from a PDF document. Uses embedded bookmarks when available, falls back to heading extraction.

Example: custom viewer with usePdfViewerState

import {
PdfViewerAnnotationLayer,
PdfViewerSearchBar,
PdfViewerToolbar,
usePdfViewerState,
} from "@osdk/react-components/experimental/pdf-viewer";
import { createPortal } from "react-dom";

function MyCustomViewer({ src }: { src: string }) {
const viewer = usePdfViewerState({ src, initialScale: 1.0 });

if (viewer.loading) return <div>Loading...</div>;
if (viewer.error) return <div>Error: {viewer.error.message}</div>;

return (
<div>
<PdfViewerToolbar
currentPage={viewer.currentPage}
numPages={viewer.numPages}
scale={viewer.scale}
sidebarOpen={viewer.sidebarOpen}
onPageChange={viewer.scrollToPage}
onScaleChange={viewer.setScale}
onSearchOpen={viewer.search.openSearch}
onSidebarToggle={viewer.toggleSidebar}
onRotateLeft={viewer.rotateLeft}
onRotateRight={viewer.rotateRight}
/>
{viewer.search.isSearchOpen && (
<PdfViewerSearchBar
query={viewer.search.query}
totalMatches={viewer.search.totalMatches}
currentMatchIndex={viewer.search.currentMatchIndex}
onQueryChange={viewer.search.setQuery}
onNext={viewer.search.nextMatch}
onPrev={viewer.search.prevMatch}
onClose={viewer.search.closeSearch}
/>
)}
<div ref={viewer.containerRef}>
<div ref={viewer.viewerRef} className="pdfViewer" />
</div>
</div>
);
}

Example: minimal viewer with usePdfViewerCore

import { usePdfViewerCore } from "@osdk/react-components/experimental/pdf-viewer";

function MinimalViewer({ src }: { src: string }) {
const { containerRef, viewerRef, loading, error, currentPage, numPages } =
usePdfViewerCore({ src });

if (loading) return <div>Loading...</div>;

return (
<div>
<span>Page {currentPage} of {numPages}</span>
<div ref={containerRef} style={{ height: "80vh", overflow: "auto" }}>
<div ref={viewerRef} className="pdfViewer" />
</div>
</div>
);
}

Types

TypeDescription
PdfViewerPropsProps for BasePdfViewer
PdfViewerMediaPropsProps for PdfViewer (extends PdfViewerProps, replaces src with media)
PdfViewerContentPropsProps for PdfViewerContent
UsePdfViewerStateOptionsOptions for usePdfViewerState
UsePdfViewerStateResultReturn type of usePdfViewerState
UsePdfViewerCoreOptionsOptions for usePdfViewerCore
UsePdfViewerCoreResultReturn type of usePdfViewerCore
UsePdfViewerResultReturn type of usePdfViewer
UsePdfViewerSearchResultReturn type of usePdfViewerSearch
AnnotationPortalTargetPortal target info returned by usePdfAnnotationPortals
PdfAnnotationAnnotation with id, type, page, rect, optional label and color
AnnotationType"highlight" | "underline" | "comment" | "pin"
SidebarMode"thumbnails" | "outline"
OutlineItemOutline entry with title, depth, pageNumber, bold, italic

Theming

All colors and sizes are driven by CSS custom properties included in @osdk/react-components/styles.css. Override them to match your application theme:

:root {
--osdk-pdf-viewer-toolbar-bg: #f5f5f5;
--osdk-pdf-viewer-toolbar-border: #e0e0e0;
--osdk-pdf-viewer-search-highlight: rgba(255, 200, 0, 0.4);
--osdk-pdf-viewer-search-selected: rgba(255, 150, 0, 0.6);
--osdk-pdf-viewer-annotation-highlight: rgba(255, 235, 59, 0.4);
--osdk-pdf-viewer-annotation-underline: #1976d2;
--osdk-pdf-viewer-annotation-comment: rgba(76, 175, 80, 0.6);
--osdk-pdf-viewer-annotation-pin: #e53935;
}