@the-pdf/react

Build your own PDF UI.

React hooks for Apryse WebViewer. Stop fighting the API and useRef boilerplate. Build your custom PDF viewer in React.

Get early access. No spam, one email when it ships.

import { PdfViewer, useZoom } from '@TODO-rename/react'

function Zoom() {
  const { zoomIn, zoomOut } = useZoom()
  return (
    <>
      <button onClick={zoomOut}>[-]</button>
      <button onClick={zoomIn}>[+]</button>
    </>
  )
}

export default () => (
  <PdfViewer documentPath="/doc.pdf">
    <Zoom />
  </PdfViewer>
)

SSR crashes, StrictMode double-mount, the 60MB asset copy: handled in one import. <PdfViewer> owns the lifecycle so you never touch that boilerplate again.

No useEffect mount dance. No "window is not defined". SSR safe by default.

Plus a shadcn registry of UI components built on the hooks. Run one command, paste the component, own the code.

Custom UI Dev
Custom UI Dev
@customui_dev
I am building my own custom UI. I call deleteAnnotation, but the annotation still shows in the list.
4388926.4K
Founder building with WebViewer
Founder building with WebViewer
@webviewer_build
Nothing in the docs about syncing React state to the document, or reading it back.
2114183.1K
josseptt
josseptt
@josseptt
The default WebViewer UI overrides my global styles.
1562842.2K

One hook per concern

Hooks that read like the rest of your app.

A taste of the API. More hooks ship with the library.

PdfViewer

One component, your DOM, no iframe. Every hook works inside it.

import { PdfViewer } from '@TODO-rename/react'

function App() {
  return (
    <PdfViewer documentPath="/contract.pdf">
      <YourToolbar />
      {/* renders in your DOM, no iframe */}
    </PdfViewer>
  )
}

useSearch

Full text search with live result counts, wired to whatever input you render.

import { useSearch } from '@TODO-rename/react'

function SearchBox() {
  const { query, setQuery, results, activeIndex, next, prev } = useSearch()

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <span>
        {results.length ? `${activeIndex + 1} / ${results.length}` : 'No matches'}
      </span>
      <button onClick={prev}>Previous</button>
      <button onClick={next}>Next</button>
    </div>
  )
}

useThumbnails

Page thumbnails as React state. Render your own rail and jump to any page.

import { useThumbnails } from '@TODO-rename/react'

function ThumbnailRail() {
  const { thumbnails, goToPage, currentPage } = useThumbnails()

  return (
    <ol>
      {thumbnails.map((thumb) => (
        <li key={thumb.page}>
          <button
            aria-current={thumb.page === currentPage}
            onClick={() => goToPage(thumb.page)}
          >
            <img src={thumb.url} alt={`Page ${thumb.page}`} />
          </button>
        </li>
      ))}
    </ol>
  )
}

useForm

Read and write form fields as React state, with the document always in sync.

import { useForm } from '@TODO-rename/react'

function FormFields() {
  const { fields, values, setValue } = useForm()

  return (
    <form>
      {fields.map((field) => (
        <label key={field.name}>
          {field.label}
          <input
            value={values[field.name] ?? ''}
            onChange={(e) => setValue(field.name, e.target.value)}
          />
        </label>
      ))}
    </form>
  )
}

On the roadmap

Vote the next hook up the list.

Click the one you need most. We build by demand.

Components

A shadcn registry, built on the hooks.

Don't want to wire the UI yourself? Drop ready-made components into your project with one command.

Build the PDF UI you actually want.

Join the waitlist. We will email you the day @the-pdf/react ships.