A framework-agnostic, universal document renderer with optional chunked loading polyrender.wisp.place/

@polyrender/react#

React component and hook for rendering documents in the browser. A thin wrapper around @polyrender/core that handles React lifecycle, cleanup, and ref-based imperative control.

Supports PDF, EPUB, DOCX, ODT, ODS, CSV/TSV, source code, plain text, and comic book archives (.cbz, .cbr, .cb7, .cbt).

Installation#

npm install @polyrender/react @polyrender/core

Install peer dependencies only for the formats you need:

npm install pdfjs-dist       # PDF
npm install epubjs           # EPUB
npm install docx-preview     # DOCX
npm install jszip            # ODT, CBZ comic archives
npm install xlsx             # ODS
npm install papaparse        # CSV/TSV
npm install highlight.js     # Code, Markdown, JSON, XML/HTML

# Comic book archives — additional optional backends:
npm install node-unrar-js    # CBR (.cbr, RAR-compressed comics)
npm install 7z-wasm          # CB7 (.cb7, 7-Zip-compressed comics)

# Comic book archives — optional exotic image format decoders:
npm install @jsquash/jxl     # JPEG XL images inside archives
npm install utif             # TIFF images inside archives

Quick Start#

import { DocumentViewer } from '@polyrender/react'
import '@polyrender/core/styles.css'

function App() {
  return (
    <DocumentViewer
      source={{ type: 'url', url: '/report.pdf' }}
      theme="dark"
      style={{ width: '100%', height: '80vh' }}
      onReady={(info) => console.log(`${info.pageCount} pages`)}
      onPageChange={(page, total) => console.log(`${page}/${total}`)}
    />
  )
}

<DocumentViewer>#

Drop-in component. Props mirror PolyRenderOptions from @polyrender/core.

Props#

Prop Type Default Description
source DocumentSource | null The document to render
format DocumentFormat auto Override format detection
theme 'dark' | 'light' | 'system' 'dark' Color theme
className string Extra CSS class on the root element
style React.CSSProperties Styles for the wrapper div (set width/height here)
initialPage number 1 Starting page
zoom number | 'fit-width' | 'fit-page' | 'auto' Initial zoom
toolbar boolean | ToolbarConfig true Toolbar visibility/config. ToolbarConfig fields: navigation, zoom, wrapToggle, fullscreen, info, download, position
showPageNumbers boolean Show page numbers
onReady (info: DocumentInfo) => void Fired when document is loaded
onPageChange (page, total) => void Fired on page navigation
onZoomChange (zoom: number) => void Fired on zoom change
onError (error: PolyRenderError) => void Fired on unrecoverable error
onLoadingChange (loading: boolean) => void Fired on loading state change
pdf PdfOptions PDF-specific options
epub EpubOptions EPUB-specific options
code CodeOptions Code-specific options
csv CsvOptions CSV/TSV-specific options
comic ComicOptions Comic archive options (JXL/TIFF decoders, format filter)

Imperative Control via Ref#

import { useRef } from 'react'
import { DocumentViewer, type DocumentViewerRef } from '@polyrender/react'
import '@polyrender/core/styles.css'

function App() {
  const viewerRef = useRef<DocumentViewerRef>(null)

  return (
    <>
      <DocumentViewer
        ref={viewerRef}
        source={{ type: 'url', url: '/report.pdf' }}
        style={{ width: '100%', height: '80vh' }}
      />
      <button onClick={() => viewerRef.current?.goToPage(1)}>First page</button>
      <button onClick={() => viewerRef.current?.setZoom('fit-width')}>Fit width</button>
    </>
  )
}

DocumentViewerRef exposes: goToPage(page), setZoom(zoom), getCurrentPage(), getPageCount(), getZoom().

useDocumentRenderer (headless hook)#

For building fully custom UI around the renderer:

import { useDocumentRenderer } from '@polyrender/react'
import '@polyrender/core/styles.css'

function CustomViewer({ url }: { url: string }) {
  const { containerRef, state, goToPage, setZoom } = useDocumentRenderer({
    source: { type: 'url', url },
    theme: 'dark',
    toolbar: false,
  })

  return (
    <div>
      <div ref={containerRef} style={{ width: '100%', height: '600px' }} />
      <div>
        <button onClick={() => goToPage(state.currentPage - 1)}>Prev</button>
        <span>{state.currentPage} / {state.totalPages}</span>
        <button onClick={() => goToPage(state.currentPage + 1)}>Next</button>
        <button onClick={() => setZoom(state.zoom * 1.2)}>Zoom In</button>
      </div>
    </div>
  )
}

Return value#

Field Type Description
containerRef RefObject<HTMLDivElement> Attach to your container element
state PolyRenderState Current viewer state
goToPage (page: number) => void Navigate to a page
setZoom (zoom) => void Set zoom level
ready boolean true after onReady fires
error PolyRenderError | null Current error, if any

PolyRenderState contains: loading, error, currentPage, totalPages, zoom, documentInfo.

Document Sources#

See @polyrender/core for full documentation on FileSource, UrlSource, PagesSource, and ChunkedSource.

Theming#

Import and apply styles from @polyrender/core:

import '@polyrender/core/styles.css'

Override CSS custom properties on the .polyrender root element:

.my-viewer .polyrender {
  --dv-bg: #1e1e2e;
  --dv-text: #cdd6f4;
  --dv-accent: #89b4fa;
}

Repository#

The source code is hosted in two locations:

This package lives under packages/react in the monorepo.

License#

Zlib