@emsun/react-doc-viewer
v1.0.1
Published
Headless React document viewer supporting PDF, DOCX, Markdown, code, images, and video
Downloads
310
Maintainers
Readme
@emsun/react-doc-viewer
A headless React document viewer. You supply all layout and styles via render props — the library handles loading and rendering documents only.
Supports: PDF, DOCX, Markdown, plain text, code, images, and video (including direct HTTPS URLs and YouTube/Vimeo embeds).
Installation
npm install @emsun/react-doc-viewerPeer dependencies:
npm install react react-domQuick start
import { DocumentViewer } from "@emsun/react-doc-viewer";
function App() {
return (
<DocumentViewer
source={{
uri: "https://example.com/report.pdf",
name: "Q4 Report",
}}
className="my-viewer"
renderLoading={({ source }) => (
<p>Loading {source.name ?? "document"}…</p>
)}
renderError={({ error, source }) => (
<p>Failed to load {source.name}: {error}</p>
)}
renderToolbar={({ state, prevPage, nextPage, zoomIn, zoomOut, setZoom }) => (
<div className="toolbar">
{state.docType === "pdf" && state.totalPages > 0 && (
<>
<button onClick={prevPage} disabled={state.currentPage <= 1}>←</button>
<span>{state.currentPage} / {state.totalPages}</span>
<button onClick={nextPage} disabled={state.currentPage >= state.totalPages}>→</button>
</>
)}
<button onClick={zoomOut}>−</button>
<span>{Math.round(state.zoom * 100)}%</span>
<button onClick={zoomIn}>+</button>
<button onClick={() => setZoom(1)}>100%</button>
</div>
)}
/>
);
}Remote URLs (HTTPS)
Pass any reachable URL as source.uri:
<DocumentViewer source={{ uri: "https://cdn.example.com/files/report.pdf" }} />
<DocumentViewer source={{ uri: "https://cdn.example.com/photo.png" }} />
<DocumentViewer source={{ uri: "https://cdn.example.com/clip.mp4", mimeType: "video/mp4" }} />| Format | HTTPS support | Notes |
|--------|---------------|--------|
| PDF | Yes | Fetched via pdf.js — host must allow CORS |
| DOCX, Markdown, text, code | Yes | Uses fetch() — CORS required |
| Images | Yes | <img src="https://…"> |
| Video (.mp4, .webm, …) | Yes | Direct file URL in <video> |
| YouTube / Vimeo | Yes | Page URL auto-embedded via <iframe> |
If a remote PDF or DOCX loads locally but fails from a URL, configure Access-Control-Allow-Origin on the file host or proxy the file through your API.
YouTube & Vimeo
Use the watch/share page URL (not a download link):
<DocumentViewer
source={{
uri: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
name: "Demo video",
}}
/>
<DocumentViewer
source={{
uri: "https://vimeo.com/123456789",
name: "Vimeo demo",
}}
/>Supported YouTube patterns: youtube.com/watch?v=, youtu.be/, /embed/, /shorts/.
Custom container
Use renderContainer when you need full control over the outer layout:
<DocumentViewer
source={source}
renderContainer={({ children }) => (
<div className="viewer-shell">{children}</div>
)}
renderToolbar={(props) => <MyToolbar {...props} />}
/>When using renderContainer, className on DocumentViewer is not applied — put layout classes on your wrapper instead.
Headless hook
Use useDocViewer when you build the UI yourself:
import {
useDocViewer,
PdfRenderer,
DocxRenderer,
detectDocType,
} from "@emsun/react-doc-viewer";
function MyViewer({ uri }: { uri: string }) {
const source = { uri };
const viewer = useDocViewer({
source,
onLoad: (s) => console.log("Ready", s.docType, s.totalPages),
onError: (err) => console.error(err),
});
const { state, dispatch, nextPage, prevPage, zoomIn, zoomOut } = viewer;
const docType = state.docType !== "unknown" ? state.docType : detectDocType(source);
return (
<div className="layout">
<aside>
<button onClick={prevPage}>Prev</button>
<button onClick={nextPage}>Next</button>
<button onClick={zoomIn}>Zoom in</button>
<button onClick={zoomOut}>Zoom out</button>
</aside>
<main>
{state.status === "loading" && <p>Loading…</p>}
{state.status === "error" && <p>{state.error}</p>}
{docType === "pdf" && (
<PdfRenderer source={source} state={state} dispatch={dispatch} />
)}
{docType === "docx" && (
<DocxRenderer source={source} state={state} dispatch={dispatch} />
)}
</main>
</div>
);
}Supported document types
| Type | Extensions / detection | Renderer | Runtime dependency |
|------|------------------------|----------|-------------------|
| PDF | .pdf | PdfRenderer | pdf.js 4.x (CDN) |
| Word | .docx, .doc | DocxRenderer | mammoth.js 1.6 (CDN) |
| Markdown | .md, .mdx | MarkdownRenderer | marked 12.x (CDN) |
| Text | .txt, .log | TextRenderer | — |
| Code | .js, .ts, .py, .json, … | TextRenderer | — |
| Image | .png, .jpg, .gif, .webp, .svg, … | ImageRenderer | — |
| Video | .mp4, .webm, .mov, YouTube, Vimeo | VideoRenderer | — |
Type is auto-detected from the file extension, mimeType, or URL shape (e.g. YouTube). Override with source.type.
interface DocSource {
uri: string; // HTTPS URL, path, or data: URI
type?: DocType; // Override auto-detection
name?: string; // Display name (toolbar, errors)
mimeType?: string; // Stricter MIME detection
}API reference
DocumentViewer props
| Prop | Type | Description |
|------|------|-------------|
| source | DocSource | Required. Document to display |
| renderContainer | (props & { children }) => ReactNode | Replace the root wrapper |
| renderLoading | ({ source }) => ReactNode | Shown while status === "loading" |
| renderError | ({ error, source }) => ReactNode | Shown when status === "error" |
| renderToolbar | ViewerRenderProps => ReactNode | Your toolbar (navigation, zoom, etc.) |
| renderCanvasToolbar | ViewerRenderProps => ReactNode | Shown when state.canvasMode is true (advanced) |
| onLoad | (state) => void | Document ready |
| onError | (error: string) => void | Load failed |
| onAnnotationsChange | (annotations) => void | Annotation state changed (advanced) |
| initialAnnotations | CanvasAnnotation[] | Pre-load annotations (advanced) |
| className | string | On default root only (ignored if renderContainer is used) |
| style | CSSProperties | On default root only |
ViewerRenderProps
Passed to renderToolbar, renderCanvasToolbar, and renderContainer:
| Property / method | Description |
|-------------------|-------------|
| state | Full ViewerState (status, docType, currentPage, totalPages, zoom, …) |
| goToPage, nextPage, prevPage | PDF page navigation |
| setZoom, zoomIn, zoomOut | Zoom (PDF and other renderers that respect state.zoom) |
| toggleCanvasMode | Toggle drawing mode flag (advanced) |
| setDrawingTool, setDrawingColor, setLineWidth | Drawing controls (advanced) |
| clearAnnotations, deleteAnnotation | Annotation management (advanced) |
| exportAnnotations, loadAnnotations | Serialize / restore annotations (advanced) |
Public exports
// Component & hook
DocumentViewer
useDocViewer
// Renderers (compose your own viewer)
PdfRenderer, DocxRenderer, MarkdownRenderer, TextRenderer
ImageRenderer, VideoRenderer
// Advanced
DrawingCanvas
viewerReducer, initialViewerState, initialCanvasState
detectDocType, generateId, getRelativePointStyling with data-rdv-* attributes
Renderers expose stable data-rdv-* hooks for CSS — no bundled styles:
[data-rdv-root] { /* default wrapper */ }
[data-rdv-toolbar] { /* your toolbar if you set data-rdv-toolbar */ }
[data-rdv-pdf-container] { /* PDF page area */ }
[data-rdv-pdf-canvas] { /* pdf.js canvas */ }
[data-rdv-docx-content] { /* converted DOCX HTML */ }
[data-rdv-markdown-content] { /* rendered markdown */ }
[data-rdv-text-content] { /* plain text / code */ }
[data-rdv-image] { /* image */ }
[data-rdv-video] { /* HTML5 video */ }
[data-rdv-video-iframe] { /* YouTube / Vimeo embed */ }PDF drawing (advanced)
The package exports DrawingCanvas, annotation types, and hook methods for a future drawing layer. v1 PdfRenderer does not mount the drawing overlay — PDFs render as pages only. To experiment, compose DrawingCanvas yourself next to PdfRenderer using useDocViewer state.
Development
Clone the repo and run the local playground:
npm install
npm run dev # http://localhost:5173 — sample docs in sidebar
npm run build # library output in dist/
npm run type-checkBuild artifacts:
dist/index.js— ESMdist/index.cjs— CommonJSdist/index.d.ts— TypeScript declarations
License
MIT © Gbenga Akinnukawe
