---
title: "How to build a React PDF viewer with react-pdf (2026)"
canonical_url: "https://www.nutrient.io/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf/"
md_url: "https://www.nutrient.io/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md"
last_updated: "2026-06-09T10:25:14.232Z"
description: "Step-by-step tutorial for building a React PDF viewer with react-pdf. Covers zoom, thumbnails, outline navigation, error handling, and TypeScript. Includes Nutrient SDK comparison."
---

**TL;DR**

This tutorial builds a React PDF viewer with `react-pdf` step by step — including page navigation, zoom, thumbnails, outline, error handling, and TypeScript types. It then shows how Nutrient’s React PDF library handles the same task when you need annotations, forms, or signatures built in.

## What is react-pdf?

[react-pdf](https://www.npmjs.com/package/react-pdf) is an open source React component library built on top of Mozilla’s [PDF.js](https://mozilla.github.io/pdf.js/). It provides `Document` and `Page` components that handle PDF rendering, along with `Outline` and `Thumbnail` components for navigation. The library has around 950K weekly downloads on npm and is maintained by [Wojciech Maj](https://github.com/wojtekmaj).

react-pdf focuses on rendering — it doesn’t include a prebuilt UI, annotations, form filling, or signatures. That makes it a good fit for read-only viewers where you want full control over the interface.

## Prerequisites

Before starting, make sure you have:

- [Node.js version 18 or later](https://nodejs.org/en/)

- A package manager — [npm](https://docs.npmjs.com/cli/v7/commands/npm) (included with Node.js) or [Yarn](https://yarnpkg.com/)

- [Vite](https://vitejs.dev/guide/#scaffolding-your-first-vite-project) for project scaffolding (the tutorial uses it below)

## Building a PDF viewer with react-pdf

This section walks through building a PDF viewer from scratch — setting up a React project, configuring the PDF.js worker, rendering pages, and adding features like navigation, zoom, thumbnails, outline support, error handling, and TypeScript types.

### Create a React project

Scaffold a new React project with Vite:

```bash

npm create vite@latest react-pdf-demo -- --template react

```

Change into the project directory and install dependencies:

```bash

cd react-pdf-demo
npm install

```

### Install react-pdf and configure the worker

Install the `react-pdf` package:

```bash

npm install react-pdf

```

Place a PDF file in the `public` directory. You can use our [demo document](https://www.nutrient.io/example.pdf) — rename it to `document.pdf`.

react-pdf depends on a PDF.js web worker for rendering. The worker must be configured in the same file where you use the `Document` component. You’ll set this up in the next step.

### Render a PDF document

Open `src/App.jsx` and replace its contents with the following:

```jsx

import { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";

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

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.min.mjs",
  import.meta.url,
).toString();

const App = () => {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);

  const onDocumentLoadSuccess = ({ numPages }) => {
    setNumPages(numPages);
  };

  const goToPrevPage = () => setPageNumber((prev) => Math.max(prev - 1, 1));
  const goToNextPage = () =>
    setPageNumber((prev) => (numPages? Math.min(prev + 1, numPages) : prev));

  return (
    <div>
      <nav>
        <button onClick={goToPrevPage}>Prev</button>
        <button onClick={goToNextPage}>Next</button>
        <p>
          Page {pageNumber} of {numPages}
        </p>
      </nav>

      <Document file="document.pdf" onLoadSuccess={onDocumentLoadSuccess}>
        <Page pageNumber={pageNumber} />
      </Document>
    </div>
  );
};

export default App;

```

The worker configuration, text layer CSS, and annotation layer CSS are all included in this single file. `Document` loads the PDF and exposes `numPages` through `onLoadSuccess`. `Page` renders one page at a time based on `pageNumber`.

Start the development server:

```bash

npm run dev

```

You can access the full code on [GitHub](https://github.com/PSPDFKit-labs/react-pdf-demo).

### Add page navigation

The code above already includes basic previous/next buttons. Here’s a more complete navigation bar with a page input field:

```jsx

<nav style={{ display: "flex", alignItems: "center", gap: "8px" }}>
  <button onClick={goToPrevPage} disabled={pageNumber <= 1}>
    Prev
  </button>
  <span>
    Page{" "}
    <input
      type="number"
      min={1}
      max={numPages || 1}
      value={pageNumber}
      onChange={(e) => {
        const page = Number(e.target.value);
        if (page >= 1 && page <= numPages) {
          setPageNumber(page);
        }
      }}
      style={{ width: "50px", textAlign: "center" }}
    />{" "}
    of {numPages}
  </span>
  <button onClick={goToNextPage} disabled={!numPages || pageNumber >= numPages}>
    Next
  </button>
</nav>

```

This adds an input field so users can jump directly to a page, and it disables the buttons at the document boundaries.

### Add zoom controls

The `Page` component accepts a `scale` prop that controls the zoom level. Add `scale` state and zoom buttons:

```jsx

import { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";

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

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.min.mjs",
  import.meta.url,
).toString();

const App = () => {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);
  const [scale, setScale] = useState(1.0);

  const onDocumentLoadSuccess = ({ numPages }) => {
    setNumPages(numPages);
  };

  const goToPrevPage = () => setPageNumber((prev) => Math.max(prev - 1, 1));
  const goToNextPage = () =>
    setPageNumber((prev) => (numPages? Math.min(prev + 1, numPages) : prev));

  const zoomIn = () => setScale((prev) => Math.min(prev + 0.25, 3));
  const zoomOut = () => setScale((prev) => Math.max(prev - 0.25, 0.5));
  const resetZoom = () => setScale(1.0);

  return (
    <div>
      <nav style={{ display: "flex", gap: "8px", alignItems: "center" }}>
        <button onClick={goToPrevPage} disabled={pageNumber <= 1}>
          Prev
        </button>
        <span>
          Page {pageNumber} of {numPages}
        </span>
        <button onClick={goToNextPage} disabled={!numPages || pageNumber >= numPages}>
          Next
        </button>
        <span>|</span>
        <button onClick={zoomOut} disabled={scale <= 0.5}>
          −
        </button>
        <span>{Math.round(scale * 100)}%</span>
        <button onClick={zoomIn} disabled={scale >= 3}>
          +
        </button>
        <button onClick={resetZoom}>Reset</button>
      </nav>

      <Document file="document.pdf" onLoadSuccess={onDocumentLoadSuccess}>
        <Page pageNumber={pageNumber} scale={scale} />
      </Document>
    </div>
  );
};

export default App;

```

The `scale` prop maps directly to the PDF.js render scale. A value of `1` is 100 percent, `1.5` is 150 percent, and so on. The buttons clamp the range between 50 percent and 300 percent.

### Add a thumbnail sidebar

react-pdf exports a `Thumbnail` component that renders a small preview of a page. You can use it to build a clickable sidebar:

```jsx

import { useState } from "react";
import { Document, Page, Thumbnail, pdfjs } from "react-pdf";

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

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.min.mjs",
  import.meta.url,
).toString();

const App = () => {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);

  const onDocumentLoadSuccess = ({ numPages }) => {
    setNumPages(numPages);
  };

  return (
    <Document file="document.pdf" onLoadSuccess={onDocumentLoadSuccess}>
      <div style={{ display: "flex", gap: "16px" }}>
        {/* Thumbnail sidebar */}
        <div
          style={{
            width: "150px",
            overflowY: "auto",
            maxHeight: "80vh",
            borderRight: "1px solid #ccc",

            padding: "8px",
          }}
        >
          {numPages &&
            Array.from({ length: numPages }, (_, index) => (
              <div
                key={index}
                onClick={() => setPageNumber(index + 1)}
                style={{
                  cursor: "pointer",
                  border:
                    pageNumber === index + 1? "2px solid #0078d4"

                      : "2px solid transparent",
                  marginBottom: "8px",
                }}
              >
                <Thumbnail pageNumber={index + 1} width={130} />
              </div>
            ))}
        </div>

        {/* Main page view */}
        <div>
          <Page pageNumber={pageNumber} />
        </div>
      </div>
    </Document>
  );
};

export default App;

```

The `Thumbnail` component accepts most of the same props as `Page` (including `width` for sizing). Clicking a thumbnail updates the `pageNumber` state to navigate to that page. The active thumbnail gets a blue border.

### Display the document outline

If the PDF has bookmarks, the `Outline` component renders them as a clickable list. Its `onItemClick` callback receives `{ pageNumber }`, which you can use to navigate:

```jsx

import { useState } from "react";
import { Document, Page, Outline, pdfjs } from "react-pdf";

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

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.min.mjs",
  import.meta.url,
).toString();

const App = () => {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);

  const onDocumentLoadSuccess = ({ numPages }) => {
    setNumPages(numPages);
  };

  return (
    <Document file="document.pdf" onLoadSuccess={onDocumentLoadSuccess}>
      <div style={{ display: "flex", gap: "16px" }}>
        {/* Outline sidebar */}
        <div
          style={{
            width: "200px",
            overflowY: "auto",
            maxHeight: "80vh",
            borderRight: "1px solid #ccc",

            padding: "8px",
          }}
        >
          <h3>Table of Contents</h3>
          <Outline onItemClick={({ pageNumber: pg }) => setPageNumber(pg)} />
        </div>

        {/* Main page view */}
        <div>
          <nav>
            <button
              onClick={() => setPageNumber((p) => Math.max(p - 1, 1))}
              disabled={pageNumber <= 1}
            >
              Prev
            </button>
            <span>
              Page {pageNumber} of {numPages}
            </span>
            <button
              onClick={() =>
                setPageNumber((p) => (numPages? Math.min(p + 1, numPages) : p))
              }
              disabled={!numPages || pageNumber >= numPages}
            >
              Next
            </button>
          </nav>
          <Page pageNumber={pageNumber} />
        </div>
      </div>
    </Document>
  );
};

export default App;

```

The `Outline` component only renders content if the PDF contains bookmarks. For PDFs without an outline, it renders nothing.

### Handle errors and loading states

The `Document` and `Page` components accept `loading`, `error`, and `noData` props for displaying a fallback user interface (UI):

```jsx

import { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";

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

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.min.mjs",
  import.meta.url,
).toString();

const App = () => {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);
  const [loadError, setLoadError] = useState(null);
  const [retryKey, setRetryKey] = useState(0);

  if (loadError) {
    return (
      <div style={{ padding: "20px", color: "red" }}>
        <h2>Failed to load PDF</h2>
        <p>{loadError.message}</p>
        <button
          onClick={() => {
            setLoadError(null);
            setRetryKey((k) => k + 1);
          }}
        >
          Retry
        </button>
      </div>
    );
  }

  return (
    <Document
      key={retryKey}
      file="document.pdf"
      onLoadSuccess={({ numPages }) => setNumPages(numPages)}
      onLoadError={(error) => setLoadError(error)}
      loading={<div style={{ padding: "20px" }}>Loading PDF…</div>}
      noData={<div style={{ padding: "20px" }}>No PDF file specified.</div>}
    >
      <Page
        pageNumber={pageNumber}
        loading={<div style={{ padding: "20px" }}>Rendering page…</div>}
      />
    </Document>
  );
};

export default App;

```

The `onLoadError` callback receives the error object. This example stores it in state and shows a retry button. Incrementing `retryKey` forces React to remount the `Document` component, which retries the PDF load. The `loading` prop displays a placeholder while the PDF downloads and parses. `noData` shows when no `file` prop is provided.

### TypeScript version

react-pdf ships its own type definitions. Here’s a typed version of the viewer with all the features from the previous sections combined:

```tsx

import { useState } from "react";
import {
  Document,
  Page,
  Thumbnail,
  Outline,
  pdfjs,
} from "react-pdf";
import type { DocumentProps } from "react-pdf";

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

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.min.mjs",
  import.meta.url,
).toString();

interface PDFViewerProps {
  file: DocumentProps["file"];
  initialPage?: number;
  initialScale?: number;
}

const PDFViewer = ({
  file,
  initialPage = 1,
  initialScale = 1.0,
}: PDFViewerProps) => {
  const [numPages, setNumPages] = useState<number>(0);
  const [pageNumber, setPageNumber] = useState<number>(initialPage);
  const [scale, setScale] = useState<number>(initialScale);
  const [loadError, setLoadError] = useState<Error | null>(null);
  const [retryKey, setRetryKey] = useState<number>(0);

  const goToPrevPage = () => setPageNumber((prev) => Math.max(prev - 1, 1));
  const goToNextPage = () =>
    setPageNumber((prev) => (numPages? Math.min(prev + 1, numPages) : prev));
  const zoomIn = () => setScale((prev) => Math.min(prev + 0.25, 3));
  const zoomOut = () => setScale((prev) => Math.max(prev - 0.25, 0.5));

  if (loadError) {
    return (
      <div>
        <p>Failed to load: {loadError.message}</p>
        <button
          onClick={() => {
            setLoadError(null);
            setRetryKey((k) => k + 1);
          }}
        >
          Retry
        </button>
      </div>
    );
  }

  return (
    <Document
      key={retryKey}
      file={file}
      onLoadSuccess={({ numPages }) => setNumPages(numPages)}
      onLoadError={setLoadError}
      loading={<div>Loading PDF…</div>}
    >
      <div style={{ display: "flex", gap: "16px" }}>
        {/* Thumbnail sidebar */}
        <aside style={{ width: "150px", overflowY: "auto" }}>
          {Array.from({ length: numPages }, (_, i) => (
            <div
              key={i}
              onClick={() => setPageNumber(i + 1)}
              style={{
                cursor: "pointer",
                border:
                  pageNumber === i + 1? "2px solid #0078d4"

                    : "2px solid transparent",
                marginBottom: "4px",
              }}
            >
              <Thumbnail pageNumber={i + 1} width={130} />
            </div>
          ))}
        </aside>

        {/* Main content */}
        <div>
          <nav style={{ display: "flex", gap: "8px", alignItems: "center" }}>
            <button onClick={goToPrevPage} disabled={pageNumber <= 1}>
              Prev
            </button>
            <span>
              Page {pageNumber} of {numPages}
            </span>
            <button onClick={goToNextPage} disabled={!numPages || pageNumber >= numPages}>
              Next
            </button>
            <span>|</span>
            <button onClick={zoomOut} disabled={scale <= 0.5}>
              −
            </button>
            <span>{Math.round(scale * 100)}%</span>
            <button onClick={zoomIn} disabled={scale >= 3}>
              +
            </button>
          </nav>

          <Outline
            onItemClick={({ pageNumber: pg }) => setPageNumber(pg)}
          />

          <Page pageNumber={pageNumber} scale={scale} />
        </div>
      </div>
    </Document>
  );
};

export default PDFViewer;

```

Key typing details:

- `DocumentProps["file"]` gives you the exact union type that `Document` accepts for its `file` prop (URL string, `ArrayBuffer`, `Uint8Array`, or a configuration object).

- `onLoadSuccess` receives a `PDFDocumentProxy` object, but destructuring `{ numPages }` is enough for most use cases.

- react-pdf also exports `PageProps`, `ThumbnailProps`, and `OutlineProps` if you need to type-check wrapper components.

## Limitations of react-pdf

react-pdf is a rendering library, not a full PDF editor. Here are its constraints:

- **No annotations** — You can’t add highlights, sticky notes, or drawings.

- **No form filling** — Interactive PDF forms (AcroForms) aren’t supported.

- **No digital signatures** — No support for signing or verifying signatures.

- **No editing** — Text editing, page reordering, and merging aren’t available.

- **No multiformat support** — Only PDF files are supported (no DOCX, XLSX, or images).

- **Limited text selection** — The text layer works but can be inconsistent with complex layouts.

- **Large file performance** — Rendering is JavaScript-based, which can slow down on PDFs with hundreds of pages.

If your app only needs to display PDFs, these limitations may not matter. If you need annotations, forms, or signatures, read on for an alternative.

## Building a PDF viewer with Nutrient Web SDK

This section shows how to build the same viewer using Nutrient Web SDK. You'll set up a React project, install the SDK, and render a PDF with a full-featured UI from the first render.

### Create a React project

Use Vite to scaffold a new React application:

```bash

npm create vite@latest nutrient-react-example -- --template react
cd nutrient-react-example

```

### Install and configure Nutrient

Install the `@nutrient-sdk/viewer` package:

```bash

npm install @nutrient-sdk/viewer

# or

yarn add @nutrient-sdk/viewer

# or

pnpm install @nutrient-sdk/viewer

```

Nutrient Web SDK loads its WebAssembly and supporting files from a local path, so copy them to the public folder. Install the copy plugin:

```bash

npm install -D rollup-plugin-copy

```

Update `vite.config.ts` to copy the SDK assets during build:

```ts

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import copy from "rollup-plugin-copy";

export default defineConfig({
  plugins: [
    copy({
      targets: [
        {
          src: "node_modules/@nutrient-sdk/viewer/dist/nutrient-viewer-lib",
          dest: "public/",
        },
      ],
      hook: "buildStart",
    }),
    react(),
  ],
});

```

### Render a PDF

Replace `src/App.tsx` with:

```tsx

import { useEffect, useRef } from "react";

function App() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const container = containerRef.current;
    let cleanup = () => {};

    (async () => {
      const NutrientViewer = (await import("@nutrient-sdk/viewer")).default;

      // Unload any previous instance.
      NutrientViewer.unload(container);

      if (container && NutrientViewer) {
        NutrientViewer.load({
          container,
          document: "/example.pdf",
          baseUrl: `${window.location.protocol}//${
            window.location.host
          }/${import.meta.env.BASE_URL?? ""}`,
        });
      }

      cleanup = () => {
        NutrientViewer.unload(container);
      };
    })();

    return cleanup;
  }, []);

  return <div ref={containerRef} style={{ height: "100vh", width: "100vw" }} />;
}

export default App;

```

Start the app:

```bash

npm run dev

```

Because Nutrient is a commercial product, you’ll see an evaluation watermark. To remove it, contact [Sales](https://www.nutrient.io/contact-sales/?=sdk).

You can find the finished code on [GitHub](https://github.com/PSPDFKit/nutrient-web-examples/tree/main/examples/react).

### What you get with no extra code

The Nutrient viewer includes:

- Toolbar with zoom, page navigation, search, and layout controls

- 17+ annotation types (highlights, stamps, shapes, freehand, and comments)

- PDF form filling and submission

- Electronic and digital signatures

- Text editing and page management

- Multiformat rendering (PDF, DOCX, XLSX, and images)

- WebAssembly-based rendering optimized for large documents

- [Screen reader](https://www.nutrient.io/guides/web/viewer/accessibility.md)-compatible text layers and keyboard navigation

## react-pdf vs. Nutrient: Feature comparison

| Feature                 | react-pdf                 | Nutrient Web SDK                          |
| ----------------------- | ------------------------- | ----------------------------------------- |
| **License**             | MIT                       | Commercial                                |
| **Bundle size**         | ~300 KB + PDF.js worker   | ~5 MB (includes WASM engine)              |
| **Prebuilt UI**         | None (build your own)     | Full toolbar, sidebar, modals             |
| **Annotations**         | Not available             | 15+ types with programmatic API           |
| **Form filling**        | Not supported             | AcroForms and XFA                         |
| **Digital signatures**  | Not supported             | Sign and verify                           |
| **Text editing**        | Not supported             | In-document editing                       |
| **File format support** | PDF only                  | PDF, DOCX, XLSX, and images               |
| **Rendering engine**    | JavaScript (PDF.js)       | WebAssembly                               |
| **Text selection**      | Basic text layer          | Advanced selection with copy/paste        |
| **Mobile support**      | Manual responsive CSS     | Built-in touch gestures and responsive UI |
| **Accessibility**       | Manual ARIA work          | WCAG-aligned by default                   |
| **Support**             | Community (GitHub issues) | Commercial support with SLA               |

## When to choose react-pdf

react-pdf is a good choice when:

- You need read-only PDF viewing — Display documents without editing or annotation features.

- You want a lightweight dependency — The MIT-licensed library adds minimal bundle overhead.

- You need full UI control — You’re building a custom viewer design and want to own every pixel.

- You’re prototyping — Quick setup for proof-of-concept work before committing to a paid solution.

## When to choose Nutrient

Nutrient fits better when:

- You need annotations, forms, or signatures — These features would take months to build from scratch.

- You’re rendering large or complex PDFs — WebAssembly rendering handles heavy documents more efficiently.

- You need multiformat support — Displaying DOCX, XLSX, or image files alongside PDFs.

- Accessibility compliance matters — Built-in screen reader support, keyboard navigation, and Web Content Accessibility Guidelines (WCAG) alignment.

- You want to ship faster — Prebuilt UI components reduce the time from prototype to production.

## Accessibility

- **react-pdf** — The text layer enables basic screen reader access, but you’ll need to add Accessible Rich Internet Applications (ARIA) attributes, keyboard navigation, and focus management yourself.

- **Nutrient Web SDK** — Ships with [screen reader](https://www.nutrient.io/guides/web/viewer/accessibility.md)-compatible text layers, full keyboard access (tab order, shortcuts, focus trapping), high-contrast modes, and accessible annotation tools. Aligned with WCAG and Section 508.

Accessibility is hard to retrofit. If your project has compliance requirements, evaluate this early.

## Conclusion

This tutorial covered building a React PDF viewer with react-pdf — from basic rendering through zoom, thumbnails, outline navigation, error handling, and TypeScript. For read-only viewing with a custom UI, react-pdf works well.

If you need annotations, forms, signatures, or multiformat support, [try Nutrient’s React PDF library](https://www.nutrient.io/try/). It ships with a complete UI and can be integrated in minutes.

## FAQ

#### How do I migrate from react-pdf to Nutrient?

See our step-by-step [migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-react-pdf.md) for side-by-side code comparisons covering viewer setup, text search, annotations, forms, thumbnails, and more.

#### How do I build a React PDF viewer with react-pdf?

Install `react-pdf` (`npm install react-pdf`), import the `Document` and `Page` components, configure the PDF.js worker in the same file, and build your own navigation controls. This tutorial walks through the complete process, including zoom, thumbnails, and outline navigation.

#### What are the main limitations of react-pdf?

react-pdf is a rendering library. It doesn’t support annotations, form filling, digital signatures, text editing, or multiformat files (DOCX, XLSX). The text layer can be inconsistent with complex layouts, and JavaScript-based rendering can slow down on large documents. [See the full limitations list](#limitations-of-react-pdf).

#### Does react-pdf support TypeScript?

Yes. react-pdf ships type definitions and exports types like `DocumentProps`, `PageProps`, `ThumbnailProps`, and `OutlineProps`. This tutorial includes a [full TypeScript implementation](#typescript-version) with a typed `PDFViewerProps` interface.
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.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)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Viewer](/blog/document-viewer.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Process Flows](/blog/process-flows.md)
- [or](/blog/sample-blog-updated.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)

