Search
Index document text, display matches, and jump to highlighted results.
Search has three parts: Search extracts the document's text, useSearch finds matches, and HighlightLayer draws a selected result. Mount Search once per viewer. Keep Pages outside it so indexing does not replace the document with a loading message.
A searchable viewer
This example searches on submit to avoid doing fuzzy matching on every keystroke. It uses the pdf-setup.ts from installation.
calculateHighlightRects uses the result's page number, match index, and matched length (falling back to searchText) to locate the match. Passing the result itself keeps highlighting tied to the submitted query even if the user has since edited the input. It returns pixel rectangles in page coordinates.
Search options and results
search(text, options) is synchronous. It returns the results and also updates searchResults in that hook instance.
| Option | Default | Meaning |
|---|---|---|
threshold | 0.7 | Minimum fuzzy similarity; use a value from 0 to 1 |
limit | 10 | Result budget shared between exact and fuzzy groups |
textSize | 100 | Characters of trailing context to include after a match |
matchDiacritics | true | Set to false to ignore Latin, Greek and Cyrillic accents; meaningful marks in other scripts stay intact |
ignoreHyphenation | false | Join a letter, hard hyphen, line break and lowercase continuation; inline compound words are preserved |
Results contain exactMatches, fuzzyMatches, and hasMoreResults. Each match has pageNumber, text, matchIndex, score, isExactMatch, and optional searchText and matchLength. Offsets and lengths refer to the original PDF text in UTF-16 code units. matchLength is present when the matched span differs from searchText.length, for example when case conversion expands a Unicode character. Pass the whole result to calculateHighlightRects to retain the correct span. Matching is case-insensitive. Searching an empty or whitespace-only query clears results.
When both groups have enough matches, exact matches receive ceil(limit / 2) slots and fuzzy matches receive the remainder. Unused slots are filled from the other group. hasMoreResults indicates that candidates exceed the shared budget. For “show more,” rerun the submitted query with a higher limit; results are not paginated.
Equivalent text and wrapped lines
Both search methods normalize canonical Unicode equivalents (café and cafe\u0301), Latin presentation ligatures (fi and fi), soft hyphens, and whitespace. These count as exact matches, including with threshold: 1. The original text is preserved for snippets and highlight offsets; mathematical compatibility symbols such as ² and ① are not converted to ordinary digits.
Search retains PDF.js hasEOL boundaries separately from the concatenated text. A phrase such as first second can therefore match two adjacent lines without incorrectly matching firstsecond. Chinese and Japanese line wraps between Han/kana characters add no space. Extraction order still comes from PDF.js; this is not a column or table reconstruction algorithm.
Hard-hyphen joining is opt-in because a line-ending hyphen may belong to a compound word. It does not join across an explicit blank line or across pages. A soft hyphen is discretionary formatting and is always ignored. Normalization uses Intl.Segmenter when a Unicode conversion changes a grapheme; include a polyfill if your browser targets do not provide it.
For custom indexes, the exported PageText type has { pageNumber, text, lineBreaks? }. lineBreaks is an immutable sorted array of UTF-16 offsets immediately after items whose hasEOL is true. Keep text as the original concatenated strings and replace the array when boundaries change. Do not insert separators into that string if you will use PDF.js highlight geometry. matchIndex and matchLength continue to address those original strings.
Practical limits
Search extracts text from all pages, even though Pages only renders a viewport's worth of pages. Mount the search panel on demand for large PDFs. For search-as-you-type, debounce the query and measure with representative documents; lowering the result limit does not stop the full-document matching pass.
Image-only scans need an OCR text layer before they can be searched. PDF text extraction can also differ from visual reading order or spacing. Check the extracted text when a phrase looks present but is not found.
Live example
Exact Search Term Highlighting
This viewer highlights only the exact search term you type
Full Context Highlighting
This viewer highlights the entire text chunk containing your search term
Responsive search in long documents
useSearch() also exposes searchAsync, cancelSearch, and isSearching.
searchAsync returns the same ranked results as search, but yields between
batches so the browser can process input. It defaults to an approximate 8 ms work
budget. Scheduling can increase total search time; the benefit is responsiveness.
A newer call to either search method cancels pending async work from that hook.
Unmounting or replacing document text also cancels it. Cancelled calls reject
with AbortError and never publish partial results. You can pass an external
AbortSignal through { signal } or adjust { timeSliceMs } when needed.
Keep the component inside <Root> and use <Search> to index the document.
Indexing failures and retry
If text extraction fails, Search keeps its children unmounted and shows an error
with a Retry button. Retry starts a new indexing attempt without reloading the
viewer. To match your application's error presentation, provide a fallback:
This fragment replaces the existing Search wrapper in the example above.
The fallback also receives the original error. It handles indexing failures;
Root.onError handles document loading failures.