npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

anyview

v0.2.3

Published

One component. Every format. Zero iframes. The universal document viewer for React.

Readme

Anyview

One component. Every format. Zero iframes.

The universal document viewer for React. It renders PDF, DOCX, XLSX, PPTX, CSV, Markdown, code, HTML, Jupyter notebooks & images natively in the browser. No iframes, no server round-trips, no uploads. Your files never leave the page.

npm version license bundle size CI stars

▶ Live demo  ·  Quick start  ·  Why Anyview?  ·  FAQ

A real document rendered in the live playground, with a selectable text layer, cross-format search, thumbnails, and annotations.


Status: v0.2, published on npm (npm i anyview), pre-1.0 and actively evolving. The flagship PDF experience (native render, text selection, search highlighting, 8 annotation tools) is implemented; reflowable formats (DOCX, XLSX, PPTX, CSV, Markdown, code, HTML, Jupyter notebooks) render, are searchable, and have page thumbnails. Full zoom UX (wheel / pinch / marquee / fit modes), two-page spread, a drag-drop empty state, and a rich imperative + callback API round it out. Try it via the playground.

Why Anyview?

Every other React document viewer either uses iframes (slow, insecure, leaks your files to Microsoft/Google's online viewers, can't render private/auth-gated documents) or supports only one format (PDF-only, image-only). Anyview renders everything natively in the browser, with no iframes, no server-side conversion, and no clunky embeds. Private files stay client-side.

| Feature | Anyview | @cyntler/react-doc-viewer | react-pdf | react-file-viewer | |---|:---:|:---:|:---:|:---:| | PDF | ✅ Native (pdf.js worker) | ✅ iframe | ✅ | ✅ | | DOCX | ✅ Native (mammoth.js) | ❌ iframe | ❌ | ✅ iframe | | XLSX | ✅ Native (SheetJS) | ❌ iframe | ❌ | ✅ iframe | | PPTX | ✅ Native (JSZip + XML) | ❌ | ❌ | ❌ | | CSV/TSV | ✅ Native (PapaParse) | ❌ | ❌ | ✅ | | Markdown | ✅ Native (react-markdown) | ❌ | ❌ | ✅ | | Code (50+ langs) | ✅ Native (Shiki) | ❌ | ❌ | ❌ | | HTML | ✅ Sanitized (DOMPurify) | ✅ iframe | ❌ | ✅ iframe | | Jupyter (.ipynb) | ✅ Native | ❌ | ❌ | ❌ | | Images (8 formats) | ✅ Native | ✅ | ❌ | ✅ | | Private files (no upload) | ✅ | ❌ public URL only | ✅ | ❌ | | Text selection | ✅ | partial | ✅ | ❌ | | Search + highlight | ✅ all formats | ❌ | ✅ PDF | ❌ | | Annotations | ✅ 8 tools (PDF) | ❌ | ❌ | ❌ | | Zoom (wheel/pinch/marquee/fit) | ✅ | ❌ | partial | ❌ | | Two-page spread | ✅ | ❌ | ❌ | ❌ | | No iframes | ✅ | ❌ | ✅ | ❌ | | Web Workers | ✅ Off-main-thread | ❌ | ✅ | ❌ | | Tree-shakeable | ✅ Load only what you need | ❌ | ✅ | ❌ | | WCAG 2.2 AA | ✅ Built-in | ❌ | ❌ | ❌ | | Bundle size | ~23 kB gzip (base) | ~40 kB | ~50 kB | ~100 kB |

Quick Start

npm install anyview

Anyview keeps format parsers as optional peer dependencies so you ship only what you use. Add the parser for each format you need:

npm install pdfjs-dist                  # PDF
npm install mammoth                     # DOCX
npm install xlsx                        # XLSX
npm install jszip                       # PPTX
npm install papaparse                   # CSV/TSV
npm install react-markdown remark-gfm   # Markdown
npm install shiki                       # code highlighting
npm install dompurify                   # HTML / DOCX sanitization

Jupyter notebooks (.ipynb) reuse the Markdown, code, and sanitizer parsers above, so they need no extra dependency.

PDF fonts and CMaps

PDF rendering runs in a Web Worker and ships its own copy of pdf.js's font/CMap/color assets (node_modules/anyview/dist/pdfjs/) so CJK/Indic text and non-embedded fonts render correctly out of the box - no CDN, no extra install.

The worker resolves these assets relative to its own script URL, which works automatically if your bundler serves anyview's files with their original relative layout intact (true for most Vite/webpack setups that don't heavily repackage dependency workers). If PDF text renders as blank boxes, your bundler moved the worker without its pdfjs/ sibling folder - copy node_modules/anyview/dist/pdfjs to a path your app serves, then point the adapter at it once at startup:

import { PdfAdapter } from 'anyview/pdf';

PdfAdapter.configureAssets('/pdfjs/'); // wherever you copied dist/pdfjs to
import { DocViewer } from 'anyview';
import 'anyview/styles';

function App() {
  return (
    <DocViewer
      source={{ kind: 'url', url: '/files/report.pdf' }}
      theme="auto"
      showToolbar
      showSidebar
      onDocumentLoad={(doc) => console.log(`${doc.pageCount} pages`)}
    />
  );
}

source accepts a File, a URL, a raw ArrayBuffer, or a FileSystemFileHandle:

<DocViewer source={{ kind: 'file', file }} />
<DocViewer source={{ kind: 'buffer', buffer, name: 'a.docx', type: '' }} />

Headline features

  • Native rendering, zero iframes. PDF rasterizes off the main thread in a Web Worker (OffscreenCanvas → ImageBitmap), while Office, text, and notebook formats parse to real DOM.
  • Real text selection. Select and copy text directly off PDF pages via an aligned, invisible text layer over the bitmap; reflowable formats select natively.
  • Search with highlighting. Cmd/Ctrl+F finds matches across every text format, paints highlight overlays, distinguishes the active match, and scrolls it into view. PDF uses the text layer; DOCX, XLSX, PPTX, Markdown, code, HTML, CSV, and notebooks use the CSS Custom Highlight API over real DOM text.
  • Full zoom & layout. Ctrl/Cmd+wheel and two-finger pinch zoom (anchored at the cursor), marquee zoom-to-selection, fit-page / fit-width / actual-size modes, and a two-page / book-spread layout.
  • Annotations, 8 tools. Highlight, free-hand ink, sticky note, rectangle, ellipse, line, arrow, and free-text on PDF pages. Geometry is normalized so they survive zoom. Create, read, update, and delete them, export/import as versioned sidecar JSON through the imperative ref, and observe changes with onAnnotationChange.
  • Jupyter notebooks. Open .ipynb files natively: markdown cells, syntax-highlighted code, and saved outputs (text, images, HTML/SVG, error tracebacks). No kernel, no Python, no upload.
  • Drop-in file opening. The empty state is a working dropzone, so you can click to browse or drop a file to open it.
  • Events & control. Rich callbacks (onPageChange, onZoom, onSearchResult, onVisiblePagesChange, onSelectionChange, onAnnotationChange) plus a controlled page prop.
  • Virtualized. Only visible pages (± overscan) are in the DOM, and a byte-budgeted LRU cache keeps rendered bitmaps under a 256 MB ceiling.
  • Accessible, themeable & SSR-ready. ARIA roles, keyboard nav, forced-colors and reduced-motion support, four themes (light / dark / auto / sepia), full i18n via the locale prop, and Next.js App Router support (ships 'use client', SSR-safe).

Supported Formats

| Format | Engine | Worker | Text select | Search | Outline | Annotations | |---|---|:---:|:---:|:---:|:---:|:---:| | PDF | pdf.js | ✅ | ✅ | ✅ | ✅ | ✅ | | DOCX | mammoth.js | Planned | ✅ | ✅ | ✅ | - | | XLSX | SheetJS | Planned | ✅ | ✅ | ✅ | - | | PPTX | JSZip + XML | Planned | ✅ | ✅ | ✅ | - | | CSV/TSV | PapaParse | Planned | ✅ | ✅ | - | - | | Markdown | react-markdown + remark-gfm | - | ✅ | ✅ | ✅ | - | | Code (50+) | Shiki | - | ✅ | ✅ | - | - | | HTML | DOMPurify | - | ✅ | ✅ | ✅ | - | | Jupyter (.ipynb) | react-markdown + Shiki | - | ✅ | ✅ | ✅ | - | | Images (8) | Native browser | - | - | - | - | - | | Plain Text | Native | - | ✅ | ✅ | - | - |

"Worker: Planned" means the format currently parses on the main thread; the architecture supports moving it to a worker without API changes.

Run the playground

pnpm install
pnpm --filter anyview-playground dev

Drag any PDF / DOCX / XLSX / PPTX / CSV / Markdown / code / HTML / .ipynb / image onto the page. It renders entirely client-side; nothing is uploaded.

Architecture

┌─────────────────────────────────────────────────┐
│                   DocViewer                       │
│  ┌─────────┐  ┌──────────┐  ┌─────────────────┐  │
│  │ Toolbar │  │ Sidebar  │  │  Viewer         │  │
│  │ (zoom,  │  │ (thumbs, │  │  (virtualized   │  │
│  │  search,│  │  outline,│  │  page list +    │  │
│  │  annot) │  │  attach) │  │  text/search/   │  │
│  └─────────┘  └──────────┘  │  annot layers)  │  │
│                             └─────────────────┘  │
│              ┌─────────────────┐                  │
│              │  Zustand Store  │                  │
│              │  (6 slices)     │                  │
│              └────────┬────────┘                  │
│              ┌────────┴────────┐                  │
│              │ Adapter Registry│ (fresh instance  │
│              │  (plugin arch)  │  per document)   │
│              └────────┬────────┘                  │
│     ┌──────────┬──────┴───┬──────────┐            │
│     ▼          ▼          ▼          ▼            │
│  ┌─────┐   ┌─────┐    ┌─────┐    ┌─────┐          │
│  │ PDF │   │DOCX │    │XLSX │    │ ... │          │
│  │worker│  │     │    │     │    │     │          │
│  └─────┘   └─────┘    └─────┘    └─────┘          │
│  pdfjs-dist  mammoth   xlsx       ...             │
│  (dynamic import, never in the base bundle)       │
└─────────────────────────────────────────────────┘

Key design decisions

  • Plugin architecture. Each format is a self-contained adapter with a static manifest (loaded synchronously, so the toolbar knows capabilities before any parser loads) and a lazy loader (dynamic import() only when a matching file opens). The registry constructs a fresh adapter instance per document, so multiple viewers never share state.
  • Worker-offloaded. PDF parsing and rendering run in a Web Worker via Comlink, so the main thread never blocks.
  • Byte-budgeted LRU cache. Rendered page bitmaps are cached under a 256 MB budget with automatic eviction and memory-pressure shrinking.
  • WCAG 2.2 AA. ARIA roles, keyboard navigation, screen-reader announcements, forced-colors, and reduced-motion support, built in.
  • ~23 kB gzip base. Only Zustand + Comlink + @tanstack/react-virtual ship in the base; format engines load on demand as separate chunks.

Advanced Usage

Custom adapter

Adapters are loaded as classes: the module's default export is a constructor the registry instantiates per document.

import { DocViewer, createRegistry, registerBuiltInAdapters } from 'anyview';

const registry = createRegistry();
registerBuiltInAdapters(registry);

registry.register(
  {
    id: 'epub',
    label: 'EPUB Book',
    extensions: ['epub'],
    mimeTypes: ['application/epub+zip'],
    icon: '<svg>…</svg>',
    features: { search: true, annotations: false, textSelection: true, /* … */ },
    priority: 100,
    protocolVersion: 1,
  },
  () => import('./adapters/epub'), // default-exports the EpubAdapter class
);

function App() {
  return <DocViewer source={source} registry={registry} />;
}

Imperative API

const ref = useRef<DocViewerRef>(null);

ref.current?.goToPage(5);
ref.current?.zoomIn();
ref.current?.search({ text: 'invoice', caseSensitive: false, wholeWord: false, regex: false, diacritics: false });
ref.current?.nextMatch();
await ref.current?.print();

// Programmatic annotations for review / markup flows:
ref.current?.setActiveTool('rectangle');
ref.current?.addAnnotation(annotation);
const sidecar = ref.current?.exportAnnotations();   // { version: 1, annotations: [...] }
ref.current?.importAnnotations(sidecar);            // a sidecar object or its JSON string

Events & controlled props

<DocViewer
  source={source}
  page={page}                                  // controlled: drives the current page
  onPageChange={(page, total) => setPage(page)}
  onZoom={(zoom, fitMode) => {}}
  onSearchResult={(result) => {}}
  onVisiblePagesChange={(pages) => {}}          // e.g. lazy-load thumbnails
  onSelectionChange={({ text, pageIndex }) => {}}
/>

Annotations

Eight tools are available: highlight, ink, sticky note, rectangle, ellipse, line, arrow, and free-text. Geometry is normalized so annotations survive zoom. Persist them as versioned sidecar JSON, or drive them through the imperative API (addAnnotation / setAnnotations / exportAnnotations / importAnnotations).

import { serializeAnnotations, parseAnnotations } from 'anyview';

<DocViewer
  source={source}
  onAnnotationChange={(annotations) => localStorage.setItem('notes', serializeAnnotations(annotations))}
/>

Hooks & i18n

import { useNavigation, I18nProvider, registerStrings } from 'anyview';

function PageIndicator() {
  const { currentPage, totalPages } = useNavigation();
  return <span>{currentPage + 1} / {totalPages}</span>;
}

// Localize: register a strings table, then pass locale="fr"
registerStrings('fr', frenchStrings);
<DocViewer source={source} locale="fr" />

Theming

.my-app .dv-root {
  --dv-toolbar-height: 40px;
  --dv-progress-bar-color: #e63946;
  --dv-sidebar-width: 240px;
}

Four built-in themes: light, dark, auto (follows system), sepia.

Browser Support

| Feature | Chrome 90+ | Firefox 88+ | Safari 15+ | Edge 90+ | |---|:---:|:---:|:---:|:---:| | Core viewer | ✅ | ✅ | ✅ | ✅ | | Web Workers / OffscreenCanvas | ✅ | ✅ | 17+ | ✅ | | CSS Custom Highlight (DOM search) | ✅ | ✅ | 17.2+ | ✅ | | File System Access | ✅ | - | - | ✅ |

Missing features degrade gracefully. For example, rendering falls back to a main-thread canvas without OffscreenCanvas, and search still counts and navigates matches where the Custom Highlight API is unavailable.

Development

pnpm install
pnpm lint        # eslint
pnpm typecheck   # tsc --noEmit
pnpm test        # vitest
pnpm build       # vite library build

FAQ

How do I view a DOCX / XLSX / PPTX file in React without an iframe? Use Anyview. It renders Office formats natively in the browser (DOCX via mammoth.js, XLSX via SheetJS, PPTX via JSZip), so there is no iframe and no Microsoft/Google Online viewer involved:

import { DocViewer } from 'anyview';
import 'anyview/styles';

<DocViewer source={{ kind: 'file', file }} />   // a File from an <input>, drag-drop, etc.

Can it render private or local files (not just public URLs)? Yes. Everything runs client-side, so private, authenticated, and local files work and nothing is ever uploaded. This is the main difference from iframe-based viewers like @cyntler/react-doc-viewer / react-doc-viewer, which render Office files through the Microsoft Office Online service and therefore require publicly accessible URLs and send your documents to a third party.

How do I render any document type in React with one component? <DocViewer source={…} /> auto-detects the format from the file name / MIME type and loads the matching renderer on demand: PDF, DOCX, XLSX, PPTX, CSV, Markdown, code, HTML, Jupyter notebooks, and images, all from one component.

Is there a free, open-source alternative to PSPDFKit / Nutrient / Apryse for viewing Office documents in React? Yes. Anyview is MIT-licensed and free. It renders PDF and Office formats natively (no server, no license key) with text selection, cross-format search, and PDF annotations.

Which packages do I need to install? Just anyview plus the parser for each format you actually use (they're optional peer dependencies): pdfjs-dist for PDF, mammoth for DOCX, xlsx for XLSX, jszip for PPTX, papaparse for CSV, react-markdown + remark-gfm for Markdown, shiki for code, dompurify for HTML. You ship only what you use.

Does it work with Next.js / Vite / CRA? Yes. It's a standard React component (React 18+). For SSR frameworks like Next.js, render it client-side (the parsers use browser APIs and a Web Worker).

Contributing

Contributions are welcome! See CONTRIBUTING.md.

License

MIT