Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelog
  1. Home
  2. Blog
  3. Component guides
  4. React PDF Viewer: Search, Highlights, Real Annotations
Auf Deutsch lesen
  1. Blog
  2. Component guides
  3. React PDF Viewer: Search, Highlights, Real Annotations
Component guides
Component guides

React PDF Viewer: Search, Highlights, Real Annotations

SR
Serban Rusu · Founder of Wingo UI
Oct 19, 2026 · 9 min read

On this page

0%
  1. What should a React PDF viewer handle?
  2. Which React PDF viewer libraries should I compare?
  3. Why build on PDFium in React?
  4. What props does the PDF viewer need?
  5. How do I add a React PDF viewer highlight feature?
  6. How do I export PDF annotations from React?
  7. How does the PDF viewer work on a phone?
  8. Is the PDF viewer accessible?
  9. What are the common pitfalls?
  10. How do I add it to my project?
  11. FAQ
    1. What is the best free React PDF viewer?
    2. How do I highlight text in a PDF in React?
    3. Can I save highlights into the PDF file itself?
    4. What is the difference between pdf.js and PDFium?
    5. Is the Wingo UI PDF Viewer free?

TL;DR

A production React PDF viewer needs virtualized pages that stay sharp when zoomed, text search with a result counter, thumbnails, selectable text, and a layout that still works on a phone. If people mark up the document, the highlights should be plain JSON your app can store and also export into the file as real PDF annotations that Acrobat, Preview and Chrome can read. The Wingo UI PDF Viewer does this on PDFium compiled to WebAssembly, with the highlights controlled from React state.

Published Oct 19, 2026

You have a contract, an invoice or a research paper as a PDF, and your app has to show it. An <iframe> hands the job to the browser's built-in viewer, which you cannot style, translate or listen to, and it looks different in every browser. A React PDF viewer gives you that control back: your toolbar, your search, your events. The hard parts come after the first page renders: keeping a 200-page file fast, keeping text sharp at 300%, making it usable on a phone, and letting people highlight passages without losing them when the file leaves your app. This guide covers those parts with the Wingo UI PDF Viewer. We build Wingo UI, so we are not neutral; outside facts link to their sources.

What should a React PDF viewer handle?

A viewer people work in needs seven things, and only the first page render is easy with any library:

  1. Virtualized pages. Only the pages on screen render, so a long file opens as fast as a short one.
  2. Sharp zoom. At high zoom a single canvas per page gets huge, so zoomed pages paint in tiles.
  3. Search with every hit marked, a "2 of 5" counter and Enter to step through results.
  4. Thumbnails to jump through the file, and page rotation that the download keeps.
  5. Selectable text, so people can copy a clause instead of retyping it.
  6. A phone layout: a swipe scrolls, a long press selects, panels slide over the page.
  7. Highlights and notes, if people review the document, stored where your app can read them.

react-pdf, the most common starting point, leaves point 7 to you entirely.

Which React PDF viewer libraries should I compare?

Compare react-pdf, react-pdf-highlighter, react-pdf-viewer, EmbedPDF and the Wingo UI PDF Viewer. Each sits on one of two engines: Mozilla's pdf.js (opens in a new tab) (JavaScript, Apache 2.0) or Google's PDFium (opens in a new tab) (C++, the engine inside Chromium) compiled to WebAssembly. Facts are from each project's GitHub README and npm listing, as of October 2026.

OptionEngineLicenseWhat you getHighlights
react-pdf (opens in a new tab)pdf.jsMITDocument, Page, Thumbnail and Outline components with text and annotation layers; you build the toolbar and searchNot built in
react-pdf-highlighter (opens in a new tab)pdf.jsMITText and image highlights, scroll to a highlightKept in your app; the README does not describe writing them into the file
react-pdf-viewer (opens in a new tab)pdf.js (per its GitHub topics)Commercial license, bought on its siteSearch, thumbnails, rotation, printing and a default layoutA highlight plugin on npm; the repository was archived on March 25, 2026
EmbedPDF (opens in a new tab)EmbedPDF Runtime, a PDFium forkApache 2.0 per its README (the 2.15.1 npm packages list MIT)A drop-in viewer, or tree-shakable plugins to build your own UIHighlight, sticky note, free text and ink annotations, per its README
Wingo UI PDF ViewerPDFium through the EmbedPDF pluginsPro source componentToolbar, search, thumbnails, rotation, upload, download, highlights panelJSON in your state, exported as real PDF annotations

If you want a react pdf viewer free to use in a commercial app and you will build the UI yourself, react-pdf is the well-trodden path. EmbedPDF is the better pick when you want annotations without paying for a component. Pick react-pdf-highlighter when highlights only ever live in your database. Skip react-pdf-viewer for new projects: it is archived. The rest of this guide covers the Wingo UI viewer.

Why build on PDFium in React?

Because PDFium can both read and write a PDF. The viewer loads the PDFium binary (about 4 MB, fetched once and cached) into a web worker, so parsing and painting stay off the main thread, and one worker is shared by every viewer on the page. The same engine that renders the pages also reads the annotations already in the file and writes new ones on export. Running PDFium in React this way lets highlights round-trip through the file. Rendering happens in the browser, and a PDF someone drops onto the viewer opens from memory without an upload.

bash
npx wingo-ui@latest add pdf-viewer

The CLI copies components/ui/pdf-viewer.tsx, adds the Button, Popover, Tooltip and Dropdown Menu plus the shared hooks it uses, and installs the @embedpdf/* packages, pdf-lib (for writing rotations into downloads) and lucide-react.

tsx
import { PdfViewer } from "@/components/ui/pdf-viewer";
export default function ContractPage() {
return <PdfViewer src="/files/service-agreement.pdf" fileName="service-agreement.pdf" height="80dvh" author="Emma Carter" />;
}

src takes a same-origin URL, a CORS-enabled URL or a blob: URL. With no src, the viewer shows an empty state that asks for a file, and onPdfUpload hands you the File someone picked or dropped. Pair it with the File Upload when the PDF should also go to your server; the React file upload guide covers that side.

Scroll the contract, type a page number, open search and try a word like "fee":

Open search and type "fee": every hit is marked and the counter steps with Enter. Then drag across a sentence and pick a color in the bar that opens beside it
$ npx wingo-ui@latest add pdf-viewer
ProPDF Viewer docs

What props does the PDF viewer need?

Only src is needed, and height is the second one to set. The viewer fills the height you give it (640 by default, a number for px or any CSS length such as "100%" to fill a pane). After that, these are the props you will reach for:

  • defaultZoom: "automatic" (the default) fits the width up to 100%, so a page never opens blown up on a large screen or cut off on a phone. "fit-page", "fit-width" or a number also work. zoomLevels sets the steps of the zoom menu.
  • variant (surface, outline, plain) and radius for the frame.
  • showToolbar, showThumbnails, showSearch, showRotateControls, showDownload, showUpload to trim the chrome.
  • toolbarActions for your own buttons before the ⋯ menu: Share, Sign, Approve.
  • onActivePageChange and onDocumentLoadSuccess(numPages) to sync a "page 3 of 12" elsewhere in your UI.
  • labels for every string, merged over DEFAULT_PDF_VIEWER_LABELS, and locale for the dates in the highlights panel.

renderPageOverlay draws on top of every page and rotates with it, for citations or bounding boxes from a document extraction model; ref.scrollToPageArea(page, { top, left, width, height }) in percent of the page scrolls to one.

How do I add a React PDF viewer highlight feature?

You already have it: highlighting is on by default, and you control the highlights array. Select text and a bar opens with five colors, underline, strikethrough, Add note and Copy. Ctrl+Shift+H (⇧⌘H on a Mac) highlights the selection, or toggles the Highlighter, a mode where whatever you select is marked as soon as you let go. Click a highlight to edit its color or note, and Ctrl+Z / ⌘Z undoes.

Each highlight is plain JSON: { id, page, rects, text, color, type, note, author, createdAt }, with rects in PDF points from the top-left of the unrotated page. Page coordinates mean a stored highlight lines up at any zoom and rotation. Control the list like any input:

tsx
"use client";
import { useState } from "react";
import { PdfViewer, type PdfViewerHighlight } from "@/components/ui/pdf-viewer";
export function ContractReview({ documentId, initial }: { documentId: string; initial: PdfViewerHighlight[] }) {
const [highlights, setHighlights] = useState(initial);
return (
<PdfViewer
src={`/api/documents/${documentId}/file`}
fileName="service-agreement.pdf"
author="Emma Carter"
height="80dvh"
highlights={highlights}
onHighlightsChange={(next) => {
setHighlights(next);
// the whole list, after every add, edit, delete, undo, redo and import
void fetch(`/api/documents/${documentId}/highlights`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(next),
});
}}
/>
);
}

For one row per highlight, use onHighlightAdd, onHighlightChange and onHighlightRemove. Only onHighlightsChange fires for annotations imported from the file, which join the list once per document, merged by id.

The Highlighter is on: drag across a clause and it turns yellow as you release. Press Escape or the pen button to turn it off, then click a highlight to add a note
$ npx wingo-ui@latest add pdf-viewer
ProPDF Viewer docs

Set highlighting={false} for a read-only review: highlights you pass (or that the file carries) still show and open with their notes, but nothing can change. The Highlights panel lists every highlight with its page, note, author and date, filtered by color; showHighlightsPanel={false} hides it when your app shows its own list.

How do I export PDF annotations from React?

Call exportPdf() on the ref. It returns a Blob of the file with every highlight written as a real markup annotation (/Highlight, /Underline or /StrikeOut) with its note and author, plus any page rotations. The export works on a copy. Acrobat, Preview and Chrome show the annotations, and reopening the file in the viewer loads them back as editable highlights. To export PDF annotations without a download button, send that Blob to your server, as below.

tsx
"use client";
import { useRef } from "react";
import { Button } from "@/components/ui/button";
import { PdfViewer, type PdfViewerHandle } from "@/components/ui/pdf-viewer";
export function SignedCopy({ documentId }: { documentId: string }) {
const viewer = useRef<PdfViewerHandle>(null);
const saveAnnotatedCopy = async () => {
const blob = await viewer.current?.exportPdf();
if (!blob) return;
await fetch(`/api/documents/${documentId}/annotated`, {
method: "PUT",
headers: { "Content-Type": "application/pdf" },
body: blob,
});
};
return (
<PdfViewer
ref={viewer}
src={`/api/documents/${documentId}/file`}
author="Liam Brooks"
height={600}
toolbarActions={<Button size="sm" onClick={saveAnnotatedCopy}>Save copy</Button>}
/>
);
}

With importHighlights on (the default), markup annotations already in the file become highlights and are replaced by your list on export, so one deleted in the viewer is gone from the file too. Readers get the same export from the ⋯ menu.

How does the PDF viewer work on a phone?

The layout follows the viewer's own width, so a narrow viewer in a desktop side panel behaves like a phone too. Below 520px the thumbnail sidebar starts closed and slides over the pages. Below 600px the rotate buttons move into the ⋯ menu, and below 440px the Highlights toggle does too. The Highlights panel sits beside the pages from 900px and slides over them below.

On touch, a swipe scrolls. A long press selects the word under your finger, and dragging extends the selection word by word. The color bar pins to the bottom of the viewer (or the top, when the selection is down there), and highlight details open as a bottom sheet. Toolbar and selection bar buttons grow to 44px on coarse pointers. The mobile-first React components guide explains these rules.

Is the PDF viewer accessible?

Yes, it works with a keyboard and a screen reader. The toolbar is a role="toolbar" with labeled buttons, the page field takes Enter to jump, Up and Down to step and Escape to cancel, and the search result count is announced through a live region. The thumbnails are a multi-select listbox: Up and Down move, Home and End jump, Shift extends, Space toggles. In the Highlights panel, Up and Down move and Delete removes. Ctrl+F / ⌘F opens search only while focus is inside the viewer, so the browser's own find still works on the rest of the page. Keep in mind that a PDF is only as accessible as its text layer: a scanned file without OCR has no text to search, select or highlight.

What are the common pitfalls?

  • CORS. A PDF on another domain needs CORS headers, or the fetch fails and the viewer shows its error state. Proxy it through your own route otherwise.
  • A strict Content Security Policy. WebAssembly needs 'wasm-unsafe-eval' in script-src. The binary loads from jsDelivr by default; to self-host it, copy node_modules/@embedpdf/pdfium/dist/pdfium.wasm into public/ and pass wasmUrl="/pdfium.wasm".
  • No height. The viewer does not grow with its pages. Give it height or a parent with a fixed height and height="100%".
  • Word files. PDFium reads PDF only. For .docx, use the DOCX Viewer; the React DOCX viewer guide covers when to convert a Word file to PDF instead.

How do I add it to my project?

Run npx wingo-ui@latest add pdf-viewer. It is a Pro component (pricing), and its docs page has more examples, from a read-only review with a Sign button to citations on an invoice. For spreadsheets, the React Excel viewer guide covers .xlsx files, and the component guides category has more single-component deep dives.

Components in this post

  • PDF Viewer

    A React PDF viewer on PDFium: sharp pages, search, thumbnails, rotation, and highlights with notes that export as real PDF annotations.

    Pro
  • DOCX Viewer

    A React Word (.docx) viewer with real pages, headers and footers, thumbnails, zoom, search, comments, tracked changes and download.

    Pro
  • File Upload

    A React file upload with drag and drop, paste and camera capture: validation, previews, per-file progress, and list, grid or avatar layouts.

    Pro

FAQ

What is the best free React PDF viewer?

For a free, MIT-licensed option, react-pdf by Wojciech Maj renders pages, thumbnails and an outline on top of pdf.js, and you build the toolbar, search and highlights yourself. EmbedPDF is an open-source viewer on a PDFium fork that drops into a page. The Wingo UI PDF Viewer is a Pro component built on the EmbedPDF plugins, with a finished toolbar, a highlights panel and highlights kept as JSON in your state.

How do I highlight text in a PDF in React?

You need the text positions on the page, a way to turn a selection into rectangles in PDF points, and a layer that draws them at every zoom and rotation. The PDF Viewer does this for you: select text, pick a color in the bar beside the selection, and the highlight lands in the highlights array you pass with onHighlightsChange.

Can I save highlights into the PDF file itself?

Yes, if they are written as markup annotations. The PDF Viewer's ref.exportPdf() returns a Blob with every highlight as a /Highlight, /Underline or /StrikeOut annotation, with its note and author, so other PDF readers show them and the viewer loads them back when the file is reopened.

What is the difference between pdf.js and PDFium?

pdf.js is Mozilla's PDF renderer written in JavaScript, licensed Apache 2.0. PDFium is the C++ PDF engine that ships inside Chromium; compiled to WebAssembly, it runs in a web worker in the browser. Both render in the browser, so the file does not have to go to a server.

Is the Wingo UI PDF Viewer free?

No. The PDF Viewer is a Pro component. The Button, Tooltip, Popover and Dropdown Menu it is built from are free.

  • PDF
  • Document viewer
  • Annotations
  • React
  • Next.js

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Component guides

React DOCX Viewer: Real Pages, Comments, Tracked Changes

Build a React DOCX viewer with real pages, headers, comments and tracked changes, all in the browser, and learn when an editor or a PDF is the better call.

SRSerban Rusu·Oct 16, 2026·9 min read
Component guides

React CSV Importer: Upload, Map Columns, Fix Rows, Import

Build a React CSV importer in four steps: upload a CSV or Excel file, map its columns, fix bad rows inline, then import with progress, cancel and a report.

SRSerban Rusu·Oct 16, 2026·7 min read
Component guides

React File Upload: Drag and Drop, Paste, Camera, Progress

Build a React file upload that takes drag and drop, paste and the phone camera, checks type and size, and shows a preview and progress bar for every file.

SRSerban Rusu·Oct 16, 2026·11 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • Theme generator

Resources

  • Docs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI