React Integration
@trexolab/verifykit-react provides a React PDF viewer with signature verification, a plugin architecture, and theme support. It uses @trexolab/verifykit-core for cryptographic verification, powered by a Rust/WASM engine.
Setup
Install
npm install @trexolab/verifykit-reactpdf.js is bundled — you do not install it. Since v0.6.0 pdf.js
5.5.207ships inside this package in a lazily-loaded chunk, so it is not in yourpackage.json, not in your initial bundle, andnpm ls pdfjs-distwill not list it.@trexolab/verifykit-coreis installed automatically as a dependency.
Requirements: React 19+, modern browser with WebAssembly support. The workerUrl option is required in VerifyKitProvider config.
Import CSS
Add the stylesheet import at the top of your entry file:
import '@trexolab/verifykit-react/styles.css'Without this import, the viewer renders as a blank white area.
Copy CMap and Font Files
For non-Latin text support (CJK, Arabic, etc.), copy the CMap and font files to your public directory:
cp -r node_modules/@trexolab/verifykit-react/cmaps public/cmaps
cp -r node_modules/@trexolab/verifykit-react/standard_fonts public/standard_fontsAdd CSS Reset
html, body, #root {
height: 100%;
margin: 0;
padding: 0;
}Wrap with VerifyKitProvider
import { VerifyKitProvider } from '@trexolab/verifykit-react'
function App() {
return (
<VerifyKitProvider config={{
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
theme: { mode: 'system' },
}}>
<div style={{ height: '100vh' }}>
<MyViewer />
</div>
</VerifyKitProvider>
)
}VerifyKitProvider
The provider wraps your app to supply configuration, verifier instance, theme, and i18n context. It handles WASM initialization internally -- no setup code needed.
<VerifyKitProvider config={config}>
<App />
</VerifyKitProvider>Configuration
interface VerifyKitConfig extends VerifyKitCoreConfig {
workerUrl: string // REQUIRED. URL to the PDF.js worker script.
cMapUrl?: string // Default: '/cmaps/'
standardFontDataUrl?: string // Default: '/standard_fonts/'
theme?: {
mode?: 'light' | 'dark' | 'system'
overrides?: Record<string, string> // CSS variable overrides
}
embeddedFont?: boolean | string
toolbar?: ToolbarConfig
locale?: string // Default: 'en'
translations?: Partial<TranslationStrings>
}Key options:
workerUrl-- Required. URL to the PDF.js worker script. Use'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs'for CDN, or self-host the file and pass a local path like'/pdf.worker.min.mjs'.theme.mode-- Set the initial theme to'light','dark', or'system'(follows OS preference).cMapUrl/standardFontDataUrl-- Paths to CMap and font files for non-Latin text.toolbar-- Configure which toolbar features are enabled.
useVerification Hook
The useVerification() hook manages PDF loading, verification, and result state. It creates an async verifier backed by the Rust/WASM core.
const verification = useVerification()
// Load a file (File, ArrayBuffer, Uint8Array, or URL)
await verification.load(file)
// Access results
verification.fileBuffer // ArrayBuffer | null — the loaded PDF bytes
verification.originalFileBuffer // ArrayBuffer | null — original file bytes (before appearance swap)
verification.fileName // string — display name of the loaded file
verification.signatures // PdfSignature[] — all verified signatures
verification.status // 'valid' | 'invalid' | 'warning' | 'unknown' | 'pending' | null
verification.unsignedFields // UnsignedSigField[] — unsigned signature fields
verification.isLoading // boolean — true while verification is in progress
verification.error // LoadError | null — error object if verification failed
verification.result // VerificationResult | null — full verification result object
verification.metadata // DocumentMetadata | null — PDF document metadata
verification.permissions // DocumentPermissions | null — PDF security permissions
// Re-verify all signatures on the current buffer
await verification.revalidate()
// Reset state
verification.reset()Next.js / SSR
The package is a client module (it ships 'use client') and is safe to import
from a Server Component — pdf.js is loaded lazily inside effects, so it never
runs on the server. No next/dynamic, no ssr: false, no wrapper file. See
Deployment → Next.js.
useVerification() is also safe to call before the WASM verifier has finished
initialising: load() waits for it rather than throwing, so calling it from a
mount effect works.
Encrypted PDFs
Nothing to wire. When the viewer unlocks a password-protected PDF it hands the
accepted password to useVerification through VerifyKitProvider, so the
signature drawn on the page swaps from Adobe's yellow "?" to the validated icon
just like an unencrypted document. The only requirement is that <Viewer> and
useVerification() sit under the same provider — that provider is what
carries the password between them.
applyPassword(pwd) remains available as an escape hatch for hosts that run
their own PDF.js instance and need to report the password themselves:
// Optional — only if your app unlocks the document outside VerifyKit's viewer.
await verification.applyPassword(userPassword)Pair it with <Viewer onPasswordAccepted={verification.applyPassword} /> if you
want the report to flow through props instead of the provider. Both paths are
supported; calling both is harmless, as a repeated password is ignored.
The original signed bytes are never modified; the swap produces display-only bytes held in memory. Documents encrypted with AES-128 are supported. Other ciphers are left untouched and keep showing the "?" — see Troubleshooting.
Key concept: Verification vs Viewer
VerifyKit separates two concerns:
| Concern | Responsibility | Component |
|---|---|---|
| Verification | Cryptographic signature validation via Rust/WASM | useVerification() hook |
| Viewer | PDF rendering and UI features | <Viewer> component with plugins |
The <Viewer> does not perform verification -- it only displays pre-verified results. useVerification() loads the PDF, runs all cryptographic checks, and produces results that you pass to the viewer as props.
Viewer Component
The <Viewer> is the recommended component for displaying PDFs. It accepts plugins for composable features.
<Viewer
ref={viewerRef}
fileBuffer={verification.fileBuffer}
fileName={verification.fileName}
plugins={[layout.plugin]}
onOpenFile={handleFile}
signatures={verification.signatures}
unsignedFields={verification.unsignedFields}
verificationStatus={verification.status ?? undefined}
/>Props
| Prop | Type | Description |
|---|---|---|
fileBuffer | ArrayBuffer | null | PDF file bytes |
fileName | string | Display name |
plugins | ViewerPlugin[] | Plugins to install |
signatures | PdfSignature[] | Reactive signature data (synced to store) |
unsignedFields | UnsignedSigField[] | Reactive unsigned field data |
verificationStatus | VerificationStatus | Reactive overall verification status |
verifying | boolean | Show a loading indicator while verification is in progress |
initialState | Partial<ViewerStoreState> | Initial store overrides (for advanced use) |
onDocumentLoaded | (doc) => void | Called when PDF.js document is ready |
onOpenFile | (file) => void | File open handler (enables drag-and-drop) |
onLoadError | (error: LoadError) => void | Called when the PDF fails to load |
onRevalidate | () => void | Called when user requests re-verification |
signaturePanelOpen | boolean | Auto-open the signature panel |
isActive | boolean | Tab isolation — set to false to freeze state updates |
renderError | (error: LoadError) => ReactNode | Custom error UI renderer |
renderLayout | (slots) => ReactNode | Custom layout render function |
Ref Handle
const viewerRef = useRef<ViewerHandle>(null)
viewerRef.current.scrollToPage(5)Plugins
Viewer features are provided by plugins. You compose only the plugins you need.
Batteries-Included (defaultLayoutPlugin)
The simplest approach -- all features with zero configuration:
import { defaultLayoutPlugin } from '@trexolab/verifykit-react'
function MyViewer() {
const [layout] = useState(() => defaultLayoutPlugin())
return <Viewer fileBuffer={buffer} plugins={[layout.plugin]} />
}Always wrap plugin creation in
useStateto avoid infinite re-renders. Creating a plugin inside the render body withoutuseStateproduces a new instance every render.
Selective Features
Disable specific features you do not need:
const [layout] = useState(() =>
defaultLayoutPlugin({
disable: { print: true, download: true, contextMenu: true },
})
)Configuring Features
Sub-plugins that take options are configured under their own key:
const [layout] = useState(() =>
defaultLayoutPlugin({
zoom: { minScale: 0.5, maxScale: 4 },
// Floating previous/next control over the bottom of the page in
// single-page scroll mode, on by default. Single-page is the one mode
// where scrolling cannot reach the next page, so turning this off leaves
// the toolbar as the only way forward.
pageNavigation: { floatingNav: false },
})
)Individual Plugins
For maximum control, import and compose individual plugins:
import { zoomPlugin, pageNavigationPlugin, toolbarPlugin } from '@trexolab/verifykit-react'
const [zoom] = useState(() => zoomPlugin())
const [nav] = useState(() => pageNavigationPlugin())
const [toolbar] = useState(() => toolbarPlugin())
<Viewer fileBuffer={buffer} plugins={[zoom, nav, toolbar]} />See the Plugins guide for a complete list of all 24 plugins with their APIs and options.
WelcomeScreen
A file open/drop landing screen shown before a PDF is loaded:
import { WelcomeScreen } from '@trexolab/verifykit-react'
if (!verification.fileBuffer) {
return <WelcomeScreen onOpenFile={handleFile} />
}The WelcomeScreen displays a drop zone where users can drag and drop PDF files or click to browse. Pass onOpenFile to receive the selected File object. The accept prop controls which file types are shown in the file picker (default: '.pdf,application/pdf').
Theme Configuration
Theme Modes
Set the theme via the provider config:
<VerifyKitProvider config={{
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
theme: { mode: 'system' },
}}>Available modes:
'light'-- Light theme'dark'-- Dark theme'system'-- Follows the operating system preference
CSS Variable Overrides
Override CSS variables through the provider config:
<VerifyKitProvider config={{
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
theme: {
mode: 'dark',
overrides: {
'--primary': '#6366f1',
'--primary-hover': '#4f46e5',
},
},
}}>Runtime Theme Toggle
When using defaultLayoutPlugin, you can toggle the theme programmatically:
const [layout] = useState(() => defaultLayoutPlugin())
// Toggle between light and dark
layout.theme.toggleTheme()
// Set explicitly
layout.theme.setTheme('dark')Common Patterns
Full Example: File Loading and Verification
import { useState, useCallback, useRef } from 'react'
import {
VerifyKitProvider,
Viewer,
WelcomeScreen,
useVerification,
defaultLayoutPlugin,
} from '@trexolab/verifykit-react'
import '@trexolab/verifykit-react/styles.css'
function App() {
return (
<VerifyKitProvider config={{
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
theme: { mode: 'system' },
}}>
<div style={{ height: '100vh' }}>
<MyViewer />
</div>
</VerifyKitProvider>
)
}
function MyViewer() {
const verification = useVerification()
const viewerRef = useRef(null)
const [layout] = useState(() => defaultLayoutPlugin())
const handleFile = useCallback(
(file) => { verification.load(file) },
[verification],
)
if (!verification.fileBuffer) {
return <WelcomeScreen onOpenFile={handleFile} />
}
return (
<Viewer
ref={viewerRef}
fileBuffer={verification.fileBuffer}
fileName={verification.fileName}
plugins={[layout.plugin]}
onOpenFile={handleFile}
signatures={verification.signatures}
unsignedFields={verification.unsignedFields}
verificationStatus={verification.status ?? undefined}
/>
)
}Subscribing to Viewer State
Use useViewerStore inside a component rendered within <Viewer> to subscribe to reactive state:
import { useViewerStore, useViewerStoreKey } from '@trexolab/verifykit-react'
// Select multiple values
const [page, total] = useViewerStore(s => [s.currentPage, s.totalPages])
// Select a single key
const scale = useViewerStoreKey('scale')Next.js Setup
Since 0.6.0 there is no setup. The package ships a 'use client' directive,
so it is its own client boundary, and pdf.js is loaded lazily inside effects so
nothing browser-only is evaluated during a server render. Import it directly —
including from a Server Component — with no next/dynamic, no ssr: false,
and no wrapper file:
// app/page.tsx — a Server Component. Note: no 'use client' in this file.
import { VerifyKitProvider, Viewer } from '@trexolab/verifykit-react'
import '@trexolab/verifykit-react/styles.css'
export default function Page() {
return (
<VerifyKitProvider
config={{ workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs' }}
>
<div style={{ height: '100vh' }}>
<Viewer />
</div>
</VerifyKitProvider>
)
}Add 'use client' to your own file only when that file uses hooks itself —
which is the case as soon as you call useVerification() to load a document and
read results:
// app/verify/page.tsx
'use client'
import { useState } from 'react'
import {
VerifyKitProvider, Viewer, useVerification, defaultLayoutPlugin,
} from '@trexolab/verifykit-react'
import '@trexolab/verifykit-react/styles.css'
function Inner() {
const verification = useVerification()
const [layout] = useState(() => defaultLayoutPlugin())
return (
<div style={{ height: '100vh' }}>
<Viewer
fileBuffer={verification.fileBuffer ?? undefined}
fileName={verification.fileName}
plugins={[layout.plugin]}
onOpenFile={(file) => verification.load(file)}
signatures={verification.signatures}
unsignedFields={verification.unsignedFields}
verificationStatus={verification.status ?? undefined}
verifying={verification.isLoading}
/>
</div>
)
}
export default function Page() {
return (
<VerifyKitProvider
config={{ workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs' }}
>
<Inner />
</VerifyKitProvider>
)
}Both forms server-render and hydrate cleanly under Turbopack and webpack, in
next dev and in production builds.
No canvas alias needed. VerifyKit renders through PDF.js's browser canvas
and never imports the Node canvas package, so config.resolve.alias.canvas
workarounds you may have copied from other PDF libraries are unnecessary.
Upgrading from ≤ 0.5.14: the viewer previously had to be wrapped in
dynamic(() => import('./Viewer'), { ssr: false }), because importing it evaluated pdf.js at module scope and threwDOMMatrix is not definedunder Node. You can now delete that wrapper — but leaving it in place still works, so the upgrade is not forced.
Keyboard Shortcuts
The following shortcuts are available when using defaultLayoutPlugin:
| Shortcut | Action | Plugin |
|---|---|---|
Ctrl+= / Ctrl++ | Zoom in | zoomPlugin |
Ctrl+- | Zoom out | zoomPlugin |
Ctrl+0 | Fit page | zoomPlugin |
PageDown / N / J | Next page | pageNavigationPlugin |
PageUp / P / K | Previous page | pageNavigationPlugin |
R / Shift+R | Rotate CW / CCW | rotationPlugin |
Ctrl+F | Open search | searchPlugin |
Ctrl+P | printPlugin | |
Ctrl+S | Download | downloadPlugin |
F5 | Fullscreen | fullscreenPlugin |
H / S | Hand / Select tool | selectionPlugin |
? | Keyboard shortcuts help | shortcutHelpPlugin |
Other Components
| Component | Description |
|---|---|
<PasswordDialog> | Password prompt for encrypted PDFs |
<DocumentMessageBar> | Top status bar showing verification result |
<SignatureListPanel> | Side panel listing all signatures |
<SignaturePropertiesModal> | Tabbed signature details modal (includes a PAdES tab for ETSI signatures) |
<CertificateViewer> | Certificate chain tree viewer |
i18n
The React package exports translation utilities for internationalization:
import { t, setLocale, registerLocale } from '@trexolab/verifykit-react'
// Get a translated string
const label = t('signature.valid')
// Switch locale at runtime
setLocale('fr')
// Register a custom locale
registerLocale('fr', { 'signature.valid': 'Signature valide', /* ... */ })You can also pass locale and translations via the VerifyKitProvider config for initial setup.