I am building my own custom UI. I call deleteAnnotation, but the annotation still shows in the list.
@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.
Nothing in the docs about syncing React state to the document, or reading it back.
The default WebViewer UI overrides my global styles.
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.