# Headless

Build a completely custom PDF interface from the same document loader and page renderer the viewer uses.

`PdfViewer` is assembled from two public building blocks. Use them directly when your layout is not a toolbar and a page: comparison views, galleries, spreads, or pages embedded in a larger UI.

## Basics

`usePdfDocument(source)` loads a document; `PdfPageCanvas` renders one of its pages.

**Demo: A thumbnail strip and a fitted page**

```tsx
import { PdfPageCanvas, usePdfDocument } from '@kingtech/react-pdf-viewer';
import { useState } from 'react';

export default function Demo() {
  const { status, document, numPages, error, reload } = usePdfDocument('/samples/multipage.pdf');
  const [page, setPage] = useState(1);

  if (status === 'error') {
    return (
      <p role="alert">
        {error?.message}{' '}
        <button type="button" onClick={reload}>
          Retry
        </button>
      </p>
    );
  }
  if (status !== 'ready' || !document) return <p role="status">Loading…</p>;

  return (
    <div
      style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 16, alignItems: 'start' }}
    >
      <ol
        aria-label="Pages"
        style={{ listStyle: 'none', margin: 0, padding: 0, maxHeight: 520, overflow: 'auto' }}
      >
        {Array.from({ length: numPages }, (_, index) => index + 1).map((n) => (
          <li key={n}>
            <button
              type="button"
              aria-current={n === page ? 'page' : undefined}
              onClick={() => setPage(n)}
              style={{
                border: n === page ? '2px solid #1d4ed8' : '2px solid transparent',
                background: 'none',
                padding: 2,
              }}
            >
              <PdfPageCanvas
                document={document}
                page={n}
                fit={{ width: 96 }}
                aria-label={`Page ${n}`}
              />
            </button>
          </li>
        ))}
      </ol>
      <PdfPageCanvas
        document={document}
        page={page}
        fit={{ width: 520 }}
        aria-label={`Page ${page} of ${numPages}`}
      />
    </div>
  );
}
```

`usePdfDocument` returns `{ status, document, numPages, error, reload }`. It handles fetching, aborting when the source changes, HTTP errors and destroying documents, and never exposes a destroyed document. It accepts the same sources and request options as the viewer.

`PdfPageCanvas` renders with cancellation, double buffering, HiDPI output and the pixel budget. It sizes itself from `scale`, or from a `fit` box (`{ width, height, upscale }`), adds the page's intrinsic rotation to `rotation`, and can render `textLayer` and `annotationLayer`.

## Customization

Combine the pieces into layouts the viewer does not offer, such as a two-page spread with highlighted matches.

**Demo: A spread with search highlights**

```tsx
import { PdfPageCanvas, usePdfDocument } from '@kingtech/react-pdf-viewer';
import { useState } from 'react';

/** A two-page spread with highlighted search matches, built from the headless pieces. */
export default function Demo() {
  const { document, numPages } = usePdfDocument('/samples/multipage.pdf');
  const [left, setLeft] = useState(1);
  const [query, setQuery] = useState('viewer');

  if (!document) return <p role="status">Loading…</p>;

  return (
    <>
      <p className="demo-controls">
        <button
          type="button"
          disabled={left <= 1}
          onClick={() => setLeft((p) => Math.max(1, p - 2))}
        >
          ← Previous spread
        </button>
        <button type="button" disabled={left + 2 > numPages} onClick={() => setLeft((p) => p + 2)}>
          Next spread →
        </button>
        <label>
          Highlight <input value={query} onChange={(event) => setQuery(event.target.value)} />
        </label>
      </p>
      <div style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
        {[left, left + 1]
          .filter((n) => n <= numPages)
          .map((n) => (
            <PdfPageCanvas
              key={n}
              document={document}
              page={n}
              fit={{ width: 320 }}
              textLayer
              highlight={{ query, selected: null }}
              aria-label={`Page ${n} of ${numPages}`}
            />
          ))}
      </div>
    </>
  );
}
```

Style pages with the same `--rpv-page-*` variables as the viewer; see the [customization guide](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/customization/).

## Accessibility in headless layouts

Give each `PdfPageCanvas` an `aria-label` (“Page 3 of 10”) so it is exposed as an image, or as a group containing its text and links when `textLayer` or `annotationLayer` is on; without a label it is decorative. Keyboard navigation, focus management and live announcements are yours to provide.

## Advanced / escape hatch

`document` is a PDF.js `PDFDocumentProxy`: anything PDF.js can do (metadata, outline, text extraction, annotations) is available from it.

## Limitations

- **No toolbar, shortcuts or fullscreen.** Headless pieces render pages only. Workaround: use `PdfViewer` with [`renderToolbar`](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/custom-toolbar/) if you only need a different toolbar.
- **One `usePdfDocument` per document.** Each hook call loads its own copy. Workaround: load once near the top of your tree and pass `document` down.
- **Many canvases cost memory.** Rendering dozens of full-size pages at once is expensive. Workaround: render thumbnails with a small `scale` or `fit`, and mount large pages only when visible.

## API

- [usePdfDocument](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/api/use-pdf-document.md)
- [UsePdfDocumentOptions](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/api/use-pdf-document-options.md)
- [UsePdfDocumentResult](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/api/use-pdf-document-result.md)
- [PdfPageCanvas](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/api/pdf-page-canvas.md)
- [PdfPageCanvasProps](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/api/pdf-page-canvas-props.md)

---

## usePdfDocument reference

Loads a PDF document. Every source change aborts the previous fetch, discards its result and
destroys the previous document; unmounting does the same.

### Used by

- [Headless](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/headless.md)

### Import

```ts
import { usePdfDocument } from '@kingtech/react-pdf-viewer';
```

Signature: `usePdfDocument(source: PdfSource | null, options?: UsePdfDocumentOptions): UsePdfDocumentResult` → `UsePdfDocumentResult`

### Options

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `password` | `string` | — | No | Password for encrypted documents. Changing it reloads the document. |
| `pdfjsOptions` | `PdfJsDocumentOptions` | — | No | Per-instance `getDocument` overrides. |
| `onLoad` | `(document: PDFDocumentProxy) => void` | — | No | The document is ready. |
| `onError` | `(error: PdfViewerError) => void` | — | No | Every load failure except aborts and handled HTTP errors. |
| `requestInit` | `RequestInit` | — | No | As on `PdfViewer`. |
| `fetcher` | `(url: string, init: RequestInit) => Promise<Response>` | — | No | As on `PdfViewer`. |
| `onHttpError` | `(response: Response) => boolean \| void \| Promise<boolean \| void>` | — | No | As on `PdfViewer`. |
| `getHttpErrorMessage` | `(response: Response) => Promise<string>` | — | No | As on `PdfViewer`. |

### Source

[packages/react-pdf-viewer/src/hooks/usePdfDocument.ts](https://github.com/kiralygyula92/react-pdf-viewer/blob/main/packages/react-pdf-viewer/src/hooks/usePdfDocument.ts#L54)


---

## UsePdfDocumentOptions reference

Options for `usePdfDocument`. Changes apply on the next load, except `password`.

### Used by

- [Headless](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/headless.md)

### Import

```ts
import type { UsePdfDocumentOptions } from '@kingtech/react-pdf-viewer';
```

### Properties

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `password` | `string` | — | No | Password for encrypted documents. Changing it reloads the document. |
| `pdfjsOptions` | `PdfJsDocumentOptions` | — | No | Per-instance `getDocument` overrides. |
| `onLoad` | `(document: PDFDocumentProxy) => void` | — | No | The document is ready. |
| `onError` | `(error: PdfViewerError) => void` | — | No | Every load failure except aborts and handled HTTP errors. |
| `requestInit` | `RequestInit` | — | No | As on `PdfViewer`. |
| `fetcher` | `(url: string, init: RequestInit) => Promise<Response>` | — | No | As on `PdfViewer`. |
| `onHttpError` | `(response: Response) => boolean \| void \| Promise<boolean \| void>` | — | No | As on `PdfViewer`. |
| `getHttpErrorMessage` | `(response: Response) => Promise<string>` | — | No | As on `PdfViewer`. |

### Source

[packages/react-pdf-viewer/src/hooks/usePdfDocument.ts](https://github.com/kiralygyula92/react-pdf-viewer/blob/main/packages/react-pdf-viewer/src/hooks/usePdfDocument.ts#L16)


---

## UsePdfDocumentResult reference

State returned by `usePdfDocument`: the load status, the document once ready, its page count, the error and a reload function.

### Used by

- [Headless](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/headless.md)

### Import

```ts
import type { UsePdfDocumentResult } from '@kingtech/react-pdf-viewer';
```

### Properties

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `status` | `PdfDocumentStatus` | — | Yes | `idle` without a source (or after a handled HTTP error). |
| `document` | `PDFDocumentProxy \| null` | — | Yes | The document while `ready`. Never a destroyed document. |
| `numPages` | `number` | — | Yes | Page count; `0` without a document. |
| `error` | `PdfViewerError \| null` | — | Yes | The failure while `error`. |
| `reload` | `() => void` | — | Yes | Load the current source again. |

### Source

[packages/react-pdf-viewer/src/hooks/usePdfDocument.ts](https://github.com/kiralygyula92/react-pdf-viewer/blob/main/packages/react-pdf-viewer/src/hooks/usePdfDocument.ts#L28)


---

## PdfPageCanvas reference

Renders a single PDF page: cancellable, double-buffered, HiDPI and pixel-capped, with optional text and annotation layers.

The building block for custom viewers, together with `usePdfDocument`.

### Used by

- [Headless](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/headless.md)

### Import

```ts
import { PdfPageCanvas } from '@kingtech/react-pdf-viewer';
```

### Props

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `document` | `PDFDocumentProxy` | — | Yes | A loaded document, e.g. from `usePdfDocument`. |
| `page` | `number` | — | Yes | 1-based page; clamped to the document. |
| `scale` | `number` | `1` | No | Scale, `1` = 100%. |
| `rotation` | `Rotation` | `0` | No | Added to the page’s intrinsic rotation. |
| `fit` | `FitOptions` | — | No | A box the page must fit into. |
| `maxCanvasPixels` | `number` | `16_777_216` | No | Canvas pixel budget. |
| `textLayer` | `boolean` | `false` | No | Selectable text layer. |
| `annotationLayer` | `boolean` | `false` | No | Clickable links. |
| `highlight` | `PageHighlight` | — | No | Search matches to highlight (requires `textLayer`). |
| `aria-label` | `string` | — | No | Accessible name, e.g. `Page 1 of 3`. With a name the page is an image (a group with `textLayer`); without one it is decorative. |
| `className` | `string` | — | No | Added to the page element. |
| `style` | `CSSProperties` | — | No | Inline styles on the page element. |
| `renderError` | `(error: PdfViewerError, actions: { retry: () => void }) => ReactNode` | — | No | Replaces the in-place error view. |

### Events

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `onLinkNavigate` | `(page: number) => void` | — | No | An internal link targets another page. |
| `onHighlight` | `(element: HTMLElement) => void` | — | No | Receives the selected match’s element, e.g. to scroll it into view. |
| `onRender` | `(info: PageRenderInfo) => void` | — | No | After each completed render. |
| `onError` | `(error: PdfViewerError) => void` | — | No | Rendering failed (cancellations are not failures). |

### Source

[packages/react-pdf-viewer/src/components/PdfPageCanvas.tsx](https://github.com/kiralygyula92/react-pdf-viewer/blob/main/packages/react-pdf-viewer/src/components/PdfPageCanvas.tsx#L115)


---

## PdfPageCanvasProps reference

Props for `PdfPageCanvas`: the document and page to render, its scale, rotation and fit, the optional layers and the render callbacks.

### Used by

- [Headless](https://react-pdf-viewer-chi.vercel.app/react-pdf-viewer/headless.md)

### Import

```ts
import type { PdfPageCanvasProps } from '@kingtech/react-pdf-viewer';
```

### Properties

| Name | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `document` | `PDFDocumentProxy` | — | Yes | A loaded document, e.g. from `usePdfDocument`. |
| `page` | `number` | — | Yes | 1-based page; clamped to the document. |
| `scale` | `number` | `1` | No | Scale, `1` = 100%. |
| `rotation` | `Rotation` | `0` | No | Added to the page’s intrinsic rotation. |
| `fit` | `FitOptions` | — | No | A box the page must fit into. |
| `maxCanvasPixels` | `number` | `16_777_216` | No | Canvas pixel budget. |
| `textLayer` | `boolean` | `false` | No | Selectable text layer. |
| `annotationLayer` | `boolean` | `false` | No | Clickable links. |
| `highlight` | `PageHighlight` | — | No | Search matches to highlight (requires `textLayer`). |
| `onLinkNavigate` | `(page: number) => void` | — | No | An internal link targets another page. |
| `onHighlight` | `(element: HTMLElement) => void` | — | No | Receives the selected match’s element, e.g. to scroll it into view. |
| `aria-label` | `string` | — | No | Accessible name, e.g. `Page 1 of 3`. With a name the page is an image (a group with `textLayer`); without one it is decorative. |
| `className` | `string` | — | No | Added to the page element. |
| `style` | `CSSProperties` | — | No | Inline styles on the page element. |
| `renderError` | `(error: PdfViewerError, actions: { retry: () => void }) => ReactNode` | — | No | Replaces the in-place error view. |
| `onRender` | `(info: PageRenderInfo) => void` | — | No | After each completed render. |
| `onError` | `(error: PdfViewerError) => void` | — | No | Rendering failed (cancellations are not failures). |

### Source

[packages/react-pdf-viewer/src/components/PdfPageCanvas.tsx](https://github.com/kiralygyula92/react-pdf-viewer/blob/main/packages/react-pdf-viewer/src/components/PdfPageCanvas.tsx#L12)

