PDF Viewer

A PDF in the browser’s own viewer with a toolbar around it. It delegates the rendering on purpose, and always shows a download link — because embedded PDFs fail routinely.

src/components/ui/pdf-viewer.tsx

About

From the component's own source, where it was written.

A PDF, shown in the browser's own viewer with a toolbar around it.

**It delegates the rendering, on purpose.** Every browser ships a complete, sandboxed, accessible, keyboard-navigable PDF viewer with text selection, search and printing already built. The alternative is ~350 kB of pdf.js plus a worker, to arrive at a worse version of what is already installed. If you need what only a parser can give you — extracting text, drawing annotations, rendering a page to an image, a thumbnail rail — then reach for pdf.js directly; this component is honest that it is not that.

What it does own is the surface: a titled frame, page and zoom controls that drive the viewer through the PDF open parameters, and download and open actions that keep working when the embed does not.

**The fallback is the point of the design.** Embedded PDFs fail routinely — iOS Safari has historically rendered only the first page, some enterprise builds disable the plugin entirely, and a Content-Disposition of attachment on the response means it never displays. A viewer without a visible download link fails silently and looks broken; this one always shows the way out.

src is put in an iframe, so it must be same-origin or served with a permissive X-Frame-Options/frame-ancestors. A cross-origin PDF that refuses framing shows the fallback, which is the correct outcome rather than a blank rectangle.

Install

npx astralyx-ui add pdf-viewer

Usage

import { PdfViewer } from '@/components/ui/pdf-viewer'

<PdfViewer src="/invoice.pdf" title="Invoice 2026-04" pageCount={3} />

Composer

Change the props and the source below follows.

Astralyx UI — sample.pdf

1.2 kB

1 / 1
Props
tsx
<PdfViewer
  src="/invoice.pdf"
  title="Invoice 2026-04"
  pageCount={3}
  toolbar={true}
/>

Examples

A document with a toolbar

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

PdfViewer

Extends Omit<ComponentProps<'div'>, 'title'>.

PropTypeDefaultDescription
srcstringrequiredFramed, so it must be same-origin or serve permissive frame-ancestors. A cross-origin PDF that refuses framing shows the fallback, which is the correct outcome.
titleReactNode——
sizestring—Bytes, shown beside the title when known.
heightnumber | string520Viewer height. A PDF has no intrinsic height to grow into.
pagenumber1A hint, not a standard every viewer implements — Chrome and Firefox honour them, others ignore them.
pageCountnumber—Total pages, when the caller knows it. Enables the page counter.
zoomnumber100A hint, not a standard every viewer implements — Chrome and Firefox honour them, others ignore them.
toolbarbooleantrueHide the toolbar for a bare embed.
downloadNamestring—Suggested filename for the download action.
onPageChange(page: number) => void——
labelstring'PDF document'—
emptyLabelstring'No document.'—
fallbackReactNode—iOS Safari has rendered only the first page, enterprise builds disable the plugin, and Content-Disposition: attachment means it never displays. A viewer without a download link fails silently and looks broken.

Behaviour

TopicDetailDescription
delegates renderingon purposeEvery browser ships a sandboxed, accessible, keyboard-navigable PDF viewer with search and printing. The alternative is ~350 kB of pdf.js plus a worker to arrive at a worse version of what is installed.
when to use pdf.jsstatedExtracting text, drawing annotations, rendering a page to an image, a thumbnail rail. This component is honest that it is not that.
vs FilePreviewthe gap it fillsFilePreview dispatches images, audio, text and code, and gives PDFs an honest "no preview". This is the branch it was missing.