---
title: "react-pdf text layer: Selection, search, and redaction"
canonical_url: "https://www.nutrient.io/blog/react-pdf-text-layer-custom-renderer/"
md_url: "https://www.nutrient.io/blog/react-pdf-text-layer-custom-renderer.md"
last_updated: "2026-08-25T17:19:43.886Z"
description: "Use the react-pdf text layer for selection and copy-paste, build a custom renderer for search and redaction, and extract text from PDFs."
---

**TL;DR**

- The text layer is enabled by default — import `react-pdf/dist/Page/TextLayer.css` and you get native selection, copy-paste, and Control-F search for free.

- `customTextRenderer({ str, itemIndex })` lets you wrap or replace text per item. Its return value is inserted as HTML, so escape any untrusted input before concatenating it into tags.

- `onGetTextSuccess({ items, styles })` gives you raw text and font metadata — pair it with `renderMode="none"` and `renderTextLayer={false}` for headless extraction.

- `customTextRenderer` produces visual masking, not real redaction. The underlying text stays in the source PDF and in `onGetTextSuccess`. For true redaction, modify the PDF bytes (e.g. with `pdf-lib`) or use an SDK with a redaction API.

The text layer is an invisible HTML overlay on top of the canvas that enables text selection, copy-paste, and accessibility. `react-pdf` also supports a custom text renderer for highlighting or transforming text.

**Security note:** `customTextRenderer` returns a string that `react-pdf` injects as HTML, not text. If the PDF (or any user-controlled input you concatenate into the return value) contains characters like `<`, `>`, or `&`, the browser will parse them as markup. Always HTML-escape `str` and any user input before wrapping it in tags — the examples below use an `escapeHtml` helper for this.

## Enabling the text layer

The text layer is enabled by default. Make sure you import the CSS:

```tsx

import "react-pdf/dist/Page/TextLayer.css";

```

```tsx

<Page pageNumber={1} renderTextLayer={true} />  {/* default */}

```

To disable the text layer, set `renderTextLayer` to `false`:

```tsx

<Page pageNumber={1} renderTextLayer={false} />

```

## What the text layer does

- Overlays invisible `<span>` elements on top of the canvas, positioned to match the rendered text

- Enables native text selection (click and drag)

- Enables copy-paste (Control-C/Command-C)

- Enables the browser’s built-in find (Control-F/Command-F)

- Provides accessibility for screen readers

## Accessing text content

Use the `onGetTextSuccess` callback to access the raw text data:

```tsx

<Page
  pageNumber={1}
  onGetTextSuccess={({ items, styles }) => {
    // `items` is an array of text items.
    items.forEach((item) => {
      console.log(item.str);      // The text string.
      console.log(item.dir);      // Text direction ("ltr" or "rtl").
      console.log(item.width);    // Width in PDF points.
      console.log(item.height);   // Height in PDF points.
      console.log(item.transform); // Position/rotation matrix.
      console.log(item.hasEOL);   // Followed by line break?
    });

    // `styles` contains font information keyed by font name.
    console.log(styles);
  }}
/>

```

## Custom text renderer

The `customTextRenderer` prop lets you modify how text items are rendered. It receives each text item and returns a string (which can contain HTML):

```tsx

<Page
  pageNumber={1}
  customTextRenderer={({ str, itemIndex }) => {
    // `str`: the text string for this item.
    // `itemIndex`: index of the item in the text content array.
    return str;
  }}
/>

```

### Example: Highlighting search terms

This component wraps every match for `searchText` in a `<mark>` tag so it renders highlighted in the text layer:

```tsx

function HighlightedPage({ pageNumber, searchText }) {
  const customTextRenderer = useCallback(
    ({ str }) => {
      const escaped = escapeHtml(str);
      if (!searchText) return escaped;

      const regex = new RegExp(`(${escapeRegex(searchText)})`, "gi");
      // `$1` is the matched substring from `escaped`, so it's already HTML-safe.
      return escaped.replace(regex, '<mark class="highlight">$1</mark>');
    },
    [searchText],
  );

  return (
    <Page
      pageNumber={pageNumber}
      customTextRenderer={customTextRenderer}
    />
  );
}

function escapeHtml(s) {
  return s.replace(/[&<>"']/g, (c) => ({
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': "&quot;",
    "'": "&#39;",

  })[c]);
}

function escapeRegex(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

```

Note that `escapeRegex` covers regex metacharacters and `escapeHtml` covers HTML special characters — you need both. `escapeRegex` alone leaves you open to HTML injection if the PDF text contains tags.

```css.highlight {
  background-color: yellow;
  color: black;
  border-radius: 2px;
  padding: 0 1px;
}

```

### Example: Visually masking sensitive content

This is visual masking, not redaction. `customTextRenderer` only changes how text appears in the rendered text layer — the original text remains in the PDF file, in `onGetTextSuccess`, and in any download or print of the original document. For true redaction, you need to rewrite the PDF bytes (e.g. with [`pdf-lib`](https://pdf-lib.js.org/) or [Nutrient Web SDK’s redaction API](https://www.nutrient.io/guides/web/redaction.md)).

```tsx

function MaskedPage({ pageNumber, maskPatterns }) {
  const customTextRenderer = useCallback(
    ({ str }) => {
      let result = escapeHtml(str);
      for (const pattern of maskPatterns) {
        // Recreate the pattern with the same flags so it operates on the
        // HTML-escaped string. Patterns should target characters that
        // can't be affected by HTML escaping (digits, letters, etc.).
        result = result.replace(pattern, (match) =>
          `<span class="redacted">${"\u2588".repeat(match.length)}</span>`,
        );
      }
      return result;
    },
    [maskPatterns],
  );

  return (
    <Page
      pageNumber={pageNumber}
      customTextRenderer={customTextRenderer}
    />
  );
}

```

### Example: Adding tooltips

This renderer wraps any email address it finds in a `<span>` with a tooltip:

```tsx

const customTextRenderer = ({ str }) => {
  // Escape first, then wrap. The capture group `$1` is safe because the
  // email regex only matches characters that survive HTML escaping unchanged.
  return escapeHtml(str).replace(
    /(\S+@\S+\.\S+)/g,
    '<span title="Click to email" class="email-link">$1</span>',
  );
};

```

## Extracting all text from a PDF

This component mounts every page with rendering disabled and collects each page’s text as `onGetTextSuccess` fires:

```tsx

function TextExtractor({ file }) {
  const [allText, setAllText] = useState([]);

  const handleTextSuccess = useCallback(
    (pageNumber) =>
      ({ items }) => {
        const pageText = items.map((item) => item.str).join(" ");
        setAllText((prev) => {
          const updated = [...prev];
          updated[pageNumber - 1] = pageText;
          return updated;
        });
      },
    [],
  );

  return (
    <Document file={file} onLoadSuccess={({ numPages }) => setAllText(new Array(numPages).fill(""))}>
      {allText.map((_, i) => (
        <Page
          key={i}
          pageNumber={i + 1}
          onGetTextSuccess={handleTextSuccess(i + 1)}
          renderMode="none"      // Don't render canvas.
          renderTextLayer={false} // Don't render text layer DOM.
        />
      ))}
    </Document>
  );
}

```

## Text layer callbacks

`Page` also exposes success and error callbacks for both text extraction and text layer rendering:

```tsx

<Page
  pageNumber={1}
  onGetTextSuccess={({ items, styles }) => {
    // Text content extracted from page.
  }}
  onGetTextError={(error) => {
    // Failed to extract text.
  }}
  onRenderTextLayerSuccess={() => {
    // Text layer DOM rendered.
  }}
  onRenderTextLayerError={(error) => {
    // Failed to render text layer.
  }}
/>

```

## Key points

- Import `react-pdf/dist/Page/TextLayer.css` or text selection won’t work.

- `renderTextLayer` is `true` by default.

- `customTextRenderer` receives `{ str, itemIndex }` and returns a string (can include HTML).

- Use `customTextRenderer` for search highlighting, redaction, or text transformation.

- `onGetTextSuccess` gives you raw text data for extraction/indexing.

- `renderMode="none"` with `renderTextLayer={false}` is useful for text-only extraction without visual rendering.

## How Nutrient Web SDK handles this

Instead of building custom text renderers with regex replacement and manual CSS, Nutrient Web SDK provides text selection and search out of the box:

```js

// Built-in text selection + full-featured search.
const results = await instance.search("search term");
instance.setSearchState((state) => state.set("results", results));

// Text extraction via API.
const pageText = await instance.textLinesForPageIndex(0);

```

There’s no `customTextRenderer` with regex replacement, and no manual CSS for highlights. Nutrient provides a full-featured search UI with match counts, result navigation, case sensitivity, and whole-word matching — plus programmatic text extraction without rendering.

[Learn more about Nutrient Web SDK](https://www.nutrient.io/sdk/web-overview/) | [Migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-react-pdf.md) | [Contact Sales](https://www.nutrient.io/contact-sales/?=sdk)

## FAQ

#### Why is my text selection blank or misaligned?

You’re probably missing the text layer stylesheet. Import `react-pdf/dist/Page/TextLayer.css` before rendering any `<Page>`. Without it, the invisible text spans don’t position correctly over the canvas and selections render in the wrong place or invisibly.

#### Is <code>customTextRenderer</code> safe against XSS?

Only if you escape its return value — otherwise it’s vulnerable to cross-site scripting (XSS). `react-pdf` injects the returned string as HTML, so any unescaped `<`, `>`, or `&` in the PDF text (or in user input you concatenate in) will be parsed as markup. Use the `escapeHtml` helper shown above before adding tags.

#### Can I use <code>customTextRenderer</code> to redact sensitive data?

Not safely. It only changes how text *renders* — the original text remains in the source PDF, in `onGetTextSuccess`, and in any export of the file. For real redaction, rewrite the PDF bytes with a library like `pdf-lib`, or use an SDK that has a dedicated redaction API.

#### How do I extract text from every page without rendering the canvas?

Mount each `<Page>` with `renderMode="none"` and `renderTextLayer={false}`, and listen for `onGetTextSuccess`. PDF.js still loads the page object and runs text extraction, but it skips the canvas paint and DOM text layer, resulting in much lower memory and CPU usage for headless extraction.

#### Does <code>customTextRenderer</code> rerun on every render?

It reruns whenever the function identity changes. Wrap it in `useCallback` keyed on the values it closes over (search term, redact patterns, etc.). Otherwise, `react-pdf` rerenders the text layer on every parent render, which is wasteful and can cause flicker.

#### What’s in the <code>styles</code> object from <code>onGetTextSuccess</code>?

`styles` is a map keyed by font name with per-font metadata, including `fontFamily`, `ascent`, `descent`, and `vertical`. PDF.js uses this internally to position text spans; you can use it for things like font analysis or matching PDF styles in your own UI overlays.
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [`vector_store` holds your indexed documents (see the multimodal RAG post](/blog/agentic-rag.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Ai Legal Assistant Document Authoring](/blog/ai-legal-assistant-document-authoring.md)
- [Amazon Textract Alternatives](/blog/amazon-textract-alternatives.md)
- [Start (clears any prior buffer), navigate the document, then stop into a file.](/blog/android-faster-pdf-rendering.md)
- [Android Pdf Out Of Memory Handling](/blog/android-pdf-out-of-memory-handling.md)
- [Angular File Viewer Pdf Image Office Files](/blog/angular-file-viewer-pdf-image-office-files.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Simple PII redaction.](/blog/automated-pii-removal.md)
- [Best Document Ai Platforms](/blog/best-document-ai-platforms.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [Build Vs Buy Document Extraction](/blog/build-vs-buy-document-extraction.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [1. Extract and chunk the PDF.](/blog/chat-with-pdf.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Construction Document Data Extraction](/blog/construction-document-data-extraction.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Create And Edit Pdfs In Flutter](/blog/create-and-edit-pdfs-in-flutter.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Creating And Filling Pdf Forms Programmatically In Javascript](/blog/creating-and-filling-pdf-forms-programmatically-in-javascript.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Extraction Confidence Scores](/blog/document-extraction-confidence-scores.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Watermarking](/blog/document-watermarking.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [Extract Patient Data On Premises](/blog/extract-patient-data-on-premises.md)
- [app.py](/blog/extract-text-from-pdf-using-python.md)
- [Fillable Pdf](/blog/fillable-pdf.md)
- [How To Add Digital Signature To Pdf Using React](/blog/how-to-add-digital-signature-to-pdf-using-react.md)
- [How To Build A Dotnet Maui Pdf Viewer](/blog/how-to-build-a-dotnet-maui-pdf-viewer.md)
- [How To Build A Flutter Pdf Viewer](/blog/how-to-build-a-flutter-pdf-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [How To Build A Javascript Pdf Viewer](/blog/how-to-build-a-javascript-pdf-viewer.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Powerpoint Viewer Using Javascript](/blog/how-to-build-a-powerpoint-viewer-using-javascript.md)
- [Using Yarn](/blog/how-to-build-a-react-excel-viewer.md)
- [How To Build A React Native Pdf Viewer](/blog/how-to-build-a-react-native-pdf-viewer.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [How To Build A Reactjs File Viewer](/blog/how-to-build-a-reactjs-file-viewer.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer With Pdfjs](/blog/how-to-build-a-vuejs-pdf-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer](/blog/how-to-build-a-vuejs-pdf-viewer.md)
- [How To Build An Android Pdf Viewer](/blog/how-to-build-an-android-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Ng2 Pdf Viewer](/blog/how-to-build-an-angular-pdf-viewer-with-ng2-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Pdfjs](/blog/how-to-build-an-angular-pdf-viewer-with-pdfjs.md)
- [How To Convert Docx To Pdf Using Javascript](/blog/how-to-convert-docx-to-pdf-using-javascript.md)
- [How To Convert Docx To Pdf Using Python](/blog/how-to-convert-docx-to-pdf-using-python.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-react.md)
- [How To Convert Html To Pdf Using Wkhtmltopdf And Csharp](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-csharp.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [or](/blog/how-to-create-a-react-js-signature-pad.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Edit Pdfs Using Ios Pdf Library](/blog/how-to-edit-pdfs-using-ios-pdf-library.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [How To Extract Tables From Pdf And Images](/blog/how-to-extract-tables-from-pdf-and-images.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [How To Merge Pdfs Using Javascript](/blog/how-to-merge-pdfs-using-javascript.md)
- [How To Ocr Pdfs In Linux](/blog/how-to-ocr-pdfs-in-linux.md)
- [How To Print Pdf In Csharp](/blog/how-to-print-pdf-in-csharp.md)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Html To Pdf In Javascript](/blog/html-to-pdf-in-javascript.md)
- [Javascript Pdf Editors](/blog/javascript-pdf-editors.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Langextract Vs Llamaindex Extraction Comparison](/blog/langextract-vs-llamaindex-extraction-comparison.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [or](/blog/merge-pdfs.md)
- [Swift Package Manager](/blog/mobile-pdf-sdk.md)
- [`elements` come from your document parser — each has a type and content.](/blog/multimodal-rag.md)
- [Nutrient Flutter 6 Bindings Api](/blog/nutrient-flutter-6-bindings-api.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Building WCAG 2.2, Section 508, and PDF/UA-compliant PDFs with an SDK](/blog/pdf-accessibility.md)
- [Extract data from PDF files: A developer guide to structured data from PDFs and scans](/blog/pdf-data-extraction-developer-guide.md)
- [Pdf Extraction Benchmark Opendataloader Bench](/blog/pdf-extraction-benchmark-opendataloader-bench.md)
- [Pdf Extraction Document Case Studies](/blog/pdf-extraction-document-case-studies.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Accessibility Structtree Printing](/blog/pdfjs-accessibility-structtree-printing.md)
- [Pdfjs Advanced Loading Streaming Workers](/blog/pdfjs-advanced-loading-streaming-workers.md)
- [Pdfjs Annotation Editor Layer](/blog/pdfjs-annotation-editor-layer.md)
- [Pdfjs Area Annotations Canvas Capture](/blog/pdfjs-area-annotations-canvas-capture.md)
- [Pdfjs Coordinate Systems Pdf To Screen](/blog/pdfjs-coordinate-systems-pdf-to-screen.md)
- [Pdfjs Document Outline Bookmarks Metadata](/blog/pdfjs-document-outline-bookmarks-metadata.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [macOS](/blog/pdfjs-file-format-conversion-to-pdf.md)
- [macOS](/blog/pdfjs-generating-pdf-thumbnails-pdf2pic.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Native Annotation Layer Forms](/blog/pdfjs-native-annotation-layer-forms.md)
- [Pdfjs Navigation Zoom Rotation](/blog/pdfjs-navigation-zoom-rotation.md)
- [Pdfjs Pdf Page Manipulation Pdf Lib](/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Pdfjs Server Side Text Extraction](/blog/pdfjs-server-side-text-extraction.md)
- [Pdfjs Sticky Note Annotations](/blog/pdfjs-sticky-note-annotations.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdfjs Thumbnail Sidebar](/blog/pdfjs-thumbnail-sidebar.md)
- [Process Flows](/blog/process-flows.md)
- [React Native Pdf Annotation](/blog/react-native-pdf-annotation.md)
- [Using Yarn](/blog/react-pdf-editor.md)
- [React Pdf Loading States Errors Passwords](/blog/react-pdf-loading-states-errors-passwords.md)
- [React Pdf Setup Basic Rendering](/blog/react-pdf-setup-basic-rendering.md)
- [labels.py](/blog/route-documents-automatically-classify-api.md)
- [or](/blog/sample-blog-updated.md)
- [Sdk Product Updates Q2 2026](/blog/sdk-product-updates-q2-2026.md)
- [Add DWS MCP Server to your Claude Code project.](/blog/teaching-llms-to-read-pdfs.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [Define the HTML part of the document.](/blog/top-10-ways-to-generate-pdfs-in-python.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.md)
- [or](/blog/top-js-pdf-libraries.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [What Is Document Processing](/blog/what-is-document-processing.md)
- [What Is Intelligent Document Processing](/blog/what-is-intelligent-document-processing.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)

