Display PDFs in your React app as easily as if they were images.
—
Navigation components for PDF outline (table of contents) and thumbnail displays to enhance user experience and provide quick document navigation.
Displays PDF document outline (table of contents/bookmarks) with hierarchical navigation structure.
/**
* Displays an outline (table of contents) for the PDF document
* Should be placed inside <Document /> or passed explicit pdf prop
* @param props - Outline configuration and event handlers
* @returns React element rendering the outline or null if no outline exists
*/
function Outline(props: OutlineProps): React.ReactElement | null;
interface OutlineProps {
/** Function called when outline item is clicked */
onItemClick?: (args: OnItemClickArgs) => void;
/** Function called when outline loads successfully */
onLoadSuccess?: (outline: PDFOutline | null) => void;
/** Function called when outline fails to load */
onLoadError?: (error: Error) => void;
/** Class names for styling */
className?: ClassName;
/** React ref for the outline container div */
inputRef?: React.Ref<HTMLDivElement>;
/** PDF document proxy (usually provided by Document context) */
pdf?: PDFDocumentProxy | false;
}
type PDFOutline = OutlineNode[] | null;
interface OutlineNode {
/** Title of the outline item */
title: string;
/** Destination reference */
dest: Dest | null;
/** Child outline items */
items: OutlineNode[];
/** Whether the item is bold */
bold?: boolean;
/** Whether the item is italic */
italic?: boolean;
/** Item color in RGB */
color?: number[];
}
interface OnItemClickArgs {
/** Destination reference for navigation */
dest?: Dest;
/** Zero-based page index */
pageIndex: number;
/** One-based page number */
pageNumber: number;
}Usage Examples:
import { Document, Page, Outline } from "react-pdf";
// Basic outline display
function PDFWithOutline() {
return (
<div style={{ display: 'flex' }}>
<Document file="document-with-toc.pdf">
<div style={{ width: '200px', padding: '10px' }}>
<h3>Table of Contents</h3>
<Outline />
</div>
<div style={{ flex: 1 }}>
<Page pageNumber={1} />
</div>
</Document>
</div>
);
}
// Outline with custom navigation handling
function CustomOutlineNavigation() {
const [currentPage, setCurrentPage] = useState(1);
const handleOutlineClick = ({ pageNumber }) => {
setCurrentPage(pageNumber);
console.log('Navigating to page', pageNumber);
};
return (
<Document file="sample.pdf">
<Outline
onItemClick={handleOutlineClick}
onLoadSuccess={(outline) => {
if (outline) {
console.log('Outline loaded with', outline.length, 'items');
} else {
console.log('No outline available');
}
}}
/>
<Page pageNumber={currentPage} />
</Document>
);
}
// Styled outline
function StyledOutline() {
return (
<Document file="sample.pdf">
<Outline
className="custom-outline border p-4"
onLoadError={(error) => {
console.error('Failed to load outline:', error);
}}
/>
<Page pageNumber={1} />
</Document>
);
}Displays page thumbnails for quick navigation and document overview.
/**
* Displays a thumbnail of a page for navigation purposes
* Does not render text or annotation layers for performance
* Should be placed inside <Document /> or passed explicit pdf prop
* @param props - Thumbnail configuration extending Page props
* @returns React element rendering the page thumbnail
*/
function Thumbnail(props: ThumbnailProps): React.ReactElement;
interface ThumbnailProps extends Omit<PageProps,
| 'className'
| 'customTextRenderer'
| 'onGetAnnotationsError'
| 'onGetAnnotationsSuccess'
| 'onGetTextError'
| 'onGetTextSuccess'
| 'onRenderAnnotationLayerError'
| 'onRenderAnnotationLayerSuccess'
| 'onRenderTextLayerError'
| 'onRenderTextLayerSuccess'
| 'renderAnnotationLayer'
| 'renderForms'
| 'renderTextLayer'
> {
/** Class names for styling the thumbnail */
className?: ClassName;
/** Function called when thumbnail is clicked */
onItemClick?: (args: OnItemClickArgs) => void;
}Usage Examples:
import { Document, Page, Thumbnail } from "react-pdf";
// Basic thumbnail grid
function ThumbnailGrid() {
const [numPages, setNumPages] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
return (
<Document
file="sample.pdf"
onLoadSuccess={({ numPages }) => setNumPages(numPages)}
>
<div style={{ display: 'flex' }}>
<div style={{ width: '200px', maxHeight: '600px', overflow: 'auto' }}>
{Array.from({ length: numPages }, (_, index) => (
<Thumbnail
key={`thumb_${index + 1}`}
pageNumber={index + 1}
width={150}
className={`thumbnail ${currentPage === index + 1 ? 'active' : ''}`}
onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}
/>
))}
</div>
<div style={{ flex: 1 }}>
<Page pageNumber={currentPage} />
</div>
</div>
</Document>
);
}
// Thumbnail carousel
function ThumbnailCarousel() {
const [numPages, setNumPages] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
return (
<Document
file="sample.pdf"
onLoadSuccess={({ numPages }) => setNumPages(numPages)}
>
<div>
<Page pageNumber={currentPage} scale={1.2} />
<div style={{
display: 'flex',
overflowX: 'auto',
padding: '10px',
gap: '10px'
}}>
{Array.from({ length: numPages }, (_, index) => (
<Thumbnail
key={`thumb_${index + 1}`}
pageNumber={index + 1}
height={100}
className={`thumbnail-small ${
currentPage === index + 1 ? 'active' : ''
}`}
onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}
/>
))}
</div>
</div>
</Document>
);
}
// Thumbnail with page numbers
function ThumbnailWithNumbers() {
return (
<Document file="sample.pdf">
{Array.from({ length: 5 }, (_, index) => (
<div key={index} className="thumbnail-container">
<Thumbnail
pageNumber={index + 1}
width={120}
scale={0.3}
/>
<div className="page-number">Page {index + 1}</div>
</div>
))}
</Document>
);
}Centralized navigation handling through Document component for consistent behavior.
interface NavigationHandling {
/** Central click handler for all navigation components */
onItemClick?: (args: OnItemClickArgs) => void;
}
interface OnItemClickArgs {
/** Destination object for internal links */
dest?: Dest;
/** Zero-based page index */
pageIndex: number;
/** One-based page number */
pageNumber: number;
}
type Dest = Promise<ResolvedDest> | ResolvedDest | string | null;
type ResolvedDest = (RefProxy | number)[];Usage Examples:
// Centralized navigation handling
function PDFNavigator() {
const [currentPage, setCurrentPage] = useState(1);
const [numPages, setNumPages] = useState(null);
const handleNavigation = ({ pageNumber, dest }) => {
setCurrentPage(pageNumber);
// Handle different destination types
if (dest && typeof dest === 'string') {
// Named destination
console.log('Navigating to named destination:', dest);
} else if (dest && Array.isArray(dest)) {
// Explicit destination with coordinates
console.log('Navigating to coordinates:', dest);
}
};
return (
<Document
file="sample.pdf"
onLoadSuccess={({ numPages }) => setNumPages(numPages)}
onItemClick={handleNavigation}
>
<div style={{ display: 'flex' }}>
{/* Outline navigation */}
<div style={{ width: '250px' }}>
<h3>Contents</h3>
<Outline />
<h3>Pages</h3>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
{Array.from({ length: numPages }, (_, index) => (
<Thumbnail
key={index}
pageNumber={index + 1}
width={80}
className={currentPage === index + 1 ? 'active' : ''}
/>
))}
</div>
</div>
{/* Main page view */}
<div style={{ flex: 1 }}>
<Page pageNumber={currentPage} />
</div>
</div>
</Document>
);
}
// Navigation with history
function PDFWithHistory() {
const [pageHistory, setPageHistory] = useState([1]);
const [historyIndex, setHistoryIndex] = useState(0);
const navigateToPage = ({ pageNumber }) => {
const newHistory = pageHistory.slice(0, historyIndex + 1);
newHistory.push(pageNumber);
setPageHistory(newHistory);
setHistoryIndex(newHistory.length - 1);
};
const goBack = () => {
if (historyIndex > 0) {
setHistoryIndex(historyIndex - 1);
}
};
const goForward = () => {
if (historyIndex < pageHistory.length - 1) {
setHistoryIndex(historyIndex + 1);
}
};
const currentPage = pageHistory[historyIndex];
return (
<Document file="sample.pdf" onItemClick={navigateToPage}>
<div>
<div className="navigation-controls">
<button onClick={goBack} disabled={historyIndex === 0}>
Back
</button>
<button onClick={goForward} disabled={historyIndex === pageHistory.length - 1}>
Forward
</button>
<span>Page {currentPage}</span>
</div>
<div style={{ display: 'flex' }}>
<Outline />
<Page pageNumber={currentPage} />
</div>
</div>
</Document>
);
}Understanding and working with hierarchical outline data.
interface OutlineStructure {
/** Process nested outline items */
processOutline?: (outline: PDFOutline) => void;
/** Extract all destinations from outline */
extractDestinations?: (outline: PDFOutline) => Dest[];
/** Find outline item by destination */
findOutlineItem?: (outline: PDFOutline, dest: Dest) => OutlineNode | null;
}Usage Examples:
// Custom outline processor
function CustomOutlineProcessor() {
const [outlineData, setOutlineData] = useState(null);
const processOutline = (outline) => {
if (!outline) return;
// Flatten outline for search
const flatOutline = [];
const flatten = (items, level = 0) => {
items.forEach(item => {
flatOutline.push({ ...item, level });
if (item.items && item.items.length > 0) {
flatten(item.items, level + 1);
}
});
};
flatten(outline);
setOutlineData(flatOutline);
};
return (
<Document file="sample.pdf">
<Outline
onLoadSuccess={processOutline}
onItemClick={({ pageNumber, dest }) => {
const item = outlineData?.find(item => item.dest === dest);
console.log('Clicked outline item:', item?.title);
}}
/>
</Document>
);
}
// Outline search functionality
function SearchableOutline() {
const [outline, setOutline] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
const [filteredOutline, setFilteredOutline] = useState(null);
useEffect(() => {
if (!outline || !searchTerm) {
setFilteredOutline(outline);
return;
}
const filterOutline = (items) => {
return items.filter(item => {
const matchesSearch = item.title.toLowerCase().includes(searchTerm.toLowerCase());
const hasMatchingChildren = item.items && filterOutline(item.items).length > 0;
if (matchesSearch || hasMatchingChildren) {
return {
...item,
items: hasMatchingChildren ? filterOutline(item.items) : item.items
};
}
return false;
}).filter(Boolean);
};
setFilteredOutline(filterOutline(outline));
}, [outline, searchTerm]);
return (
<Document file="sample.pdf">
<div>
<input
type="text"
placeholder="Search outline..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Outline
onLoadSuccess={setOutline}
// Note: This is conceptual - actual filtering would need custom rendering
/>
</div>
</Document>
);
}Install with Tessl CLI
npx tessl i tessl/npm-react-pdf