react-pdf-highlight-viewer
v1.3.1
Published
A lightweight React library for rendering PDFs with text highlighting and annotated PDF download support
Maintainers
Readme
React PDF Highlighter
A lightweight, production-ready React library for rendering PDFs with text highlighting support and annotated PDF export. Built on top of react-pdf with a simple, intuitive API.
Demo

Features
✨ Simple API - Just pass a PDF file and an array of highlights
🎨 Customizable Colors - Set default or per-highlight colors
🖱️ Interactive Text Selection - Native text dragging with floating annotation popovers
🖼️ Area Highlights - Draw bounding boxes over charts or scanned pages
🔤 Case-Insensitive Matching - Optional case-insensitive text matching
📥 Download with Highlights - Export PDFs with annotations natively baked in via downloadHighlightedPdf()
🔍 Zoom Support - Both Text and Area Highlights scale automatically when zoomed
📱 Responsive - Automatically adapts to container width
⚡ Performance Optimized - Efficient DOM manipulation and memoization
🔄 Dynamic Updates - Add/remove highlights on the fly
📦 Lightweight - Minimal bundle size with peer dependencies
🎯 TypeScript Support - Full type definitions included
Installation
npm install react-pdf-highlight-viewer react react-dom react-pdf pdfjs-distPeer Dependencies
This library requires the following peer dependencies:
react^18.0.0 || ^19.0.0react-dom^18.0.0 || ^19.0.0react-pdf^10.3.0pdfjs-dist^4.0.0 || ^5.0.0
Quick Start
import { PdfHighlighter } from 'react-pdf-highlight-viewer';
import 'react-pdf-highlight-viewer/dist/react-pdf-highlight-viewer.css';
function App() {
const highlights = [
{ pageNumber: 1, content: "important text" },
{ pageNumber: 2, content: "another highlight", color: "#ff0000" }
];
return (
<PdfHighlighter
file="https://example.com/document.pdf"
highlights={highlights}
/>
);
}API Reference
<PdfHighlighter />
Main component for rendering PDFs with highlights.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| file | string \| File \| null | required | PDF source (URL, data URI, or File object) |
| highlights | Highlight[] | [] | Array of text highlights to apply |
| width | number | undefined | Page width in pixels (responsive if not set) |
| loading | React.ReactNode | <div>Loading PDF...</div> | Custom loading component |
| defaultHighlightColor | string | '#ffeb3b' | Default highlight background color |
| defaultCaseSensitive | boolean | true | Default case sensitivity for all highlights |
| onHighlightAdd | function | undefined | Callback fired when user selects text or draws an area and saves |
| enableAreaSelection | boolean | false | When true, dragging draws an Area Rectangle instead of selecting text |
| pageProps | Omit<PageProps, ...> | undefined | Additional props to pass to each Page component |
| ...documentProps | DocumentProps | - | All other react-pdf Document props are supported |
Highlight Interface
interface Highlight {
pageNumber: number; // 1-indexed page number
content?: string; // Exact text to highlight (optional if boundingRect is provided)
color?: string; // Optional custom color (overrides default)
comment?: string; // Tooltip comment when hovering over highlight
caseSensitive?: boolean; // Whether to match case-sensitively (default: true)
boundingRect?: { // Draw a coordinate-based Area Highlight
left: number; // Responsive percentage (0-100)
top: number; // Responsive percentage (0-100)
width: number; // Responsive percentage (0-100)
height: number; // Responsive percentage (0-100)
}
}downloadHighlightedPdf(options)
Utility function to download a PDF with highlight annotations baked directly into the output file. Uses a canvas-based approach with jsPDF for precise coordinate alignment.
import { downloadHighlightedPdf } from 'react-pdf-highlight-viewer';
await downloadHighlightedPdf({
file: 'https://example.com/document.pdf',
highlights: [
{ pageNumber: 1, content: 'important text', color: '#ffeb3b' }
],
fileName: 'annotated-document.pdf'
});Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| file | string \| ArrayBuffer \| Uint8Array | required | PDF source (URL, ArrayBuffer, or Uint8Array) |
| highlights | Highlight[] | required | Array of highlights to embed |
| fileName | string | 'highlighted.pdf' | Output filename for the download |
| defaultHighlightColor | string | '#ffeb3b' | Default color for highlights without a color |
| defaultCaseSensitive | boolean | true | Default case sensitivity |
| highlightOpacity | number | 0.35 | Opacity of highlight rectangles (0 to 1) |
| renderScale | number | 2 | Canvas render scale. Higher = sharper but larger files |
Usage Examples
Basic Highlighting
import { PdfHighlighter } from 'react-pdf-highlight-viewer';
import 'react-pdf-highlight-viewer/dist/react-pdf-highlight-viewer.css';
function BasicExample() {
return (
<PdfHighlighter
file="/path/to/document.pdf"
highlights={[
{ pageNumber: 1, content: "Chapter 1" }
]}
/>
);
}Custom Colors
function ColorExample() {
return (
<PdfHighlighter
file="/document.pdf"
defaultHighlightColor="#ffeb3b" // Yellow default
highlights={[
{ pageNumber: 1, content: "Important", color: "#ff0000" }, // Red
{ pageNumber: 1, content: "Note", color: "#00ff00" }, // Green
{ pageNumber: 2, content: "Warning" } // Uses default yellow
]}
/>
);
}Case-Insensitive Matching
function CaseInsensitiveExample() {
return (
<PdfHighlighter
file="/document.pdf"
highlights={[
// Will match "Hello World", "HELLO WORLD", "hello world", etc.
{ pageNumber: 1, content: "hello world", caseSensitive: false },
// Case-sensitive (default behavior)
{ pageNumber: 1, content: "Exact Match" },
// Override default with custom color
{ pageNumber: 2, content: "sample", caseSensitive: false, color: "#00ff00" }
]}
/>
);
}Zoom with Highlights
Highlights automatically persist when zooming in or out. Pass scale via pageProps:
function ZoomExample() {
const [scale, setScale] = useState(1.0);
return (
<div>
<button onClick={() => setScale(s => Math.min(s + 0.25, 3))}>Zoom In</button>
<button onClick={() => setScale(s => Math.max(s - 0.25, 0.5))}>Zoom Out</button>
<span>{Math.round(scale * 100)}%</span>
<PdfHighlighter
file="/document.pdf"
highlights={[{ pageNumber: 1, content: "highlight me" }]}
pageProps={{ scale }}
/>
</div>
);
}Download PDF with Highlights
import { PdfHighlighter, downloadHighlightedPdf } from 'react-pdf-highlight-viewer';
function DownloadExample() {
const pdfUrl = '/document.pdf';
const highlights = [
{ pageNumber: 1, content: "Important finding", color: "#4caf50" },
{ pageNumber: 2, content: "Key result", color: "#2196f3" }
];
const handleDownload = async () => {
await downloadHighlightedPdf({
file: pdfUrl,
highlights,
fileName: 'annotated-document.pdf',
highlightOpacity: 0.4
});
};
return (
<div>
<button onClick={handleDownload}>Download Annotated PDF</button>
<PdfHighlighter file={pdfUrl} highlights={highlights} />
</div>
);
}Dynamic Highlights
function DynamicExample() {
const [highlights, setHighlights] = useState([]);
const addHighlight = (text) => {
setHighlights(prev => [...prev, {
pageNumber: 1,
content: text
}]);
};
const clearHighlights = () => setHighlights([]);
return (
<div>
<button onClick={() => addHighlight("search term")}>
Add Highlight
</button>
<button onClick={clearHighlights}>
Clear All
</button>
<PdfHighlighter
file="/document.pdf"
highlights={highlights}
/>
</div>
);
}Interactive Text & Area Highlighting (New in v1.3.0)
Users can seamlessly drag to select text or draw bounding boxes for images and charts:
function InteractiveExample() {
const [highlights, setHighlights] = useState([]);
const [isAreaMode, setIsAreaMode] = useState(false);
// Fired when the user selects text or draws an area and clicks "Save"
const handleHighlightAdd = (newHighlight) => {
setHighlights(prev => [...prev, newHighlight]);
};
return (
<div>
<button onClick={() => setIsAreaMode(!isAreaMode)}>
{isAreaMode ? "Disable Area Draw" : "Enable Area Draw"}
</button>
<PdfHighlighter
file="/document.pdf"
highlights={highlights}
onHighlightAdd={handleHighlightAdd} // Enables the floating popover
enableAreaSelection={isAreaMode} // Toggles drawing bounding boxes
/>
</div>
);
}With File Upload
function FileUploadExample() {
const [file, setFile] = useState(null);
const handleFileChange = (e) => {
setFile(e.target.files[0]);
};
return (
<div>
<input type="file" accept=".pdf" onChange={handleFileChange} />
{file && (
<PdfHighlighter
file={file}
highlights={[{ pageNumber: 1, content: "example" }]}
/>
)}
</div>
);
}Using React-PDF Props
The component extends react-pdf's DocumentProps, so you can pass any Document or Page props:
function AdvancedExample() {
return (
<PdfHighlighter
file="/document.pdf"
highlights={[{ pageNumber: 1, content: "highlight me" }]}
// Document props (from react-pdf)
onLoadSuccess={(pdf) => console.log('Loaded', pdf.numPages, 'pages')}
onLoadError={(error) => console.error('Load error:', error)}
// Page props (applied to all pages)
pageProps={{
scale: 1.5, // Zoom level
renderTextLayer: true, // Enable text selection
renderAnnotationLayer: false, // Disable annotations
renderMode: 'canvas' // Render mode: 'canvas' | 'svg'
}}
/>
);
}Important Notes
Text Matching
By default, the content field must closely match the PDF's internal text representation:
- ✅ Correct:
"Hello World"(if PDF has this exact text) - ✅ Also works:
"hello world"withcaseSensitive: false - ✅ Whitespace-flexible: The library handles minor spacing differences between your text and the PDF's internal representation
Tips:
- Use
caseSensitive: falseto ignore case differences - Copy text directly from the PDF viewer to ensure accurate matching
- The library uses whitespace-flexible matching for
downloadHighlightedPdf()— minor space differences are handled automatically
Whitespace Issues
PDFs sometimes store text differently than it appears visually. If highlighting doesn't work:
- Open browser DevTools
- Inspect the PDF text layer
- Copy the exact text from the DOM
- Use that text in your
contentfield
Troubleshooting
Highlights Not Appearing
- Check text matching: Ensure
contentclosely matches PDF text - Verify page number: Page numbers are 1-indexed (first page = 1)
- Check console: Look for errors or warnings
- Inspect DOM: Verify text exists in
.react-pdf__Page__textContent
Highlights Disappearing After Zoom
This was fixed in v1.2.0. The library now detects when the text layer is rebuilt (e.g., after a zoom change) and automatically re-applies highlights to the new DOM elements.
PDF Not Loading
- CORS issues: Ensure PDF URL allows cross-origin requests
- File path: Verify the file path or URL is correct
- Worker error: The library uses a CDN-hosted PDF.js worker by default
Performance Issues
For large PDFs with many highlights:
- Limit visible pages (implement pagination)
- Debounce highlight updates
- Use
React.memoon parent components
Changelog
v1.3.0 (2026-05-12)
Added:
- ✅ Interactive Text Selection — Users can drag to select text and a floating popover automatically appears to save annotations. Triggers
onHighlightAdd. - ✅ Coordinate-Based Area Highlights — Pass
enableAreaSelection={true}to draw bounding boxes over charts or scanned pages. Stored responsively via percentages (boundingRect). - ✅ Multi-line Regex Matching — Highlighting logic now uses robust regex matching that natively ignores internal PDF whitespace and newline discrepancies.
- ✅ Area Export —
downloadHighlightedPdfnow flawlessly renders both Text Highlights and Area Highlights onto the exported PDF.
v1.2.1 (2026-05-11)
Added:
- ✅
downloadHighlightedPdf()utility — export PDFs with highlights baked in using jsPDF + canvas rendering - ✅ Whitespace-flexible text matching in download utility — handles spacing differences between PDF text items and highlight strings
- ✅ Multiple simultaneous highlights — select and display multiple highlights at once without overwriting
- ✅ Zoom persistence — highlights survive scale changes via proper text layer lifecycle management
- ✅ Input validation and edge case hardening (color parsing, stale DOM detection, bounds checks)
- ✅ Demo recording included in package
Fixed:
- 🐛 Multi-highlight overwrite bug — highlights on shared spans no longer destroy each other (single-pass rebuild)
- 🐛 Zoom highlight loss — text layer re-render now correctly re-captures spans and re-applies marks
- 🐛 Long text passage matching — whitespace-flexible matching prevents false negatives from PDF spacing
Dependencies:
- Added
jspdfas a runtime dependency for PDF export
v1.1.4 (2026-02-14)
Updated:
- ✅ Documentation - Updated README with complete changelog
v1.1.3 (2026-02-14)
Fixed:
- ✅ CSS export configuration - Removed
exportsfield restriction to fix Vite import issues - ✅ Package now works seamlessly with Vite and other bundlers
v1.1.0 (2026-02-13)
Added:
- ✅ React 18 support - Package now supports both React 18.x and React 19.x
- ✅ Backward compatibility with React 18.0.0+
Updated:
- Peer dependencies now accept
react^18.0.0 || ^19.0.0 - Peer dependencies now accept
react-dom^18.0.0 || ^19.0.0
v1.0.0 (2026-02-09)
Initial Release:
- ✅ PDF rendering with text highlighting
- ✅ Case-sensitive and case-insensitive matching
- ✅ Custom highlight colors
- ✅ React-PDF props passthrough
- ✅ TypeScript support
- ✅ Comprehensive test suite (42 tests)
- ✅ Production-ready with full documentation
Browser Support
- Chrome/Edge: ✅ Latest 2 versions
- Firefox: ✅ Latest 2 versions
- Safari: ✅ Latest 2 versions
License
MIT
Contributing
Contributions are welcome! Please open an issue or PR on GitHub.
Credits
Built with:
