File Preview
Look at a file where it is instead of opening another tab. PDFs page through with selectable text, images zoom and pan, and one set of controls fits, zooms, rotates and pages every kind.
Installation#
Usage#
Hand the root a file (a File from an input or a drop) or a src. It works out how to show it from the type and the name, and every part below reads the file, the zoom and the page from it. PDFs render with Mozilla's PDF.js, which is loaded only when a PDF is opened.
import {
FilePreview,
FilePreviewHeader,
FilePreviewIcon,
FilePreviewInfo,
FilePreviewTitle,
FilePreviewMeta,
FilePreviewActions,
FilePreviewDownload,
FilePreviewViewport,
FilePreviewToolbar,
FilePreviewZoomOut,
FilePreviewZoomMenu,
FilePreviewZoomIn,
FilePreviewFit,
} from "@/components/ui/file-preview"
<FilePreview file={file}>
<FilePreviewHeader>
<FilePreviewIcon />
<FilePreviewInfo>
<FilePreviewTitle />
<FilePreviewMeta />
</FilePreviewInfo>
<FilePreviewActions>
<FilePreviewDownload />
</FilePreviewActions>
</FilePreviewHeader>
<FilePreviewViewport>
<FilePreviewToolbar>
<FilePreviewZoomOut />
<FilePreviewZoomMenu />
<FilePreviewZoomIn />
<FilePreviewFit />
</FilePreviewToolbar>
</FilePreviewViewport>
</FilePreview>Preview an upload#
The flow it exists for: files arrive in a File Upload list and a click opens them over the page with FilePreviewDialog, instead of in a new tab. Drop your own files in, they preview straight from the disk and never leave the browser. The dialog is edge to edge on a phone, lands focus on the canvas so the keys work at once, and keeps showing the file while it fades out, so clearing it on close is safe.
Every kind of file#
The renderer follows the file. A PDF pages through with selectable text and a page rail. An image zooms and, once it no longer fits, drags to pan (double-click toggles 100%). Text and code read as a numbered sheet. Video plays in the Video Player and audio in a compact player whose seek bar is the clip's own waveform. Anything else says so and offers its download. A control with nothing to do for the file renders nothing, and the bar folds the separator it leaves behind.
Fit and zoom#
"fit" shows the whole page and "width" fills the canvas side to side; both follow the canvas as it resizes. A number is a fixed level, 1 being the file's actual size. The buttons walk the familiar steps, the readout opens every level, and ⌘ + wheel or a pinch zooms continuously. Every change holds a point still, the pointer for a wheel or a pinch and the centre otherwise, so a zoom never throws you somewhere else in the file. A photo never grows past its own pixels on "fit": an upscaled photo is a blurred one.
Docked toolbar#
The toolbar floats over the canvas when it sits in FilePreviewViewport. Put it in the header instead, with variant="plain", for a bar docked above the document like a desktop reader. The same controls, the same behaviour. This one opens at defaultZoom="width", the reading layout.
Variants#
The frame follows the library's contours. Default is the edge ring with the smallest lift, outline the ring alone, and plain drops the frame for a preview that sits in a surface which already has one: a pane, a card, the dialog (which uses it). None of them paints a fill; the canvas is the one ground, so the document reads the same on every surface.
Only the parts you need#
Every part can go. Here the canvas stands alone, with no header and no bar: an image inline in a card or a chat message. The keys, the wheel and the pinch still zoom it, because they live on the canvas, not on the buttons. Leave out the rail, the page field or any single control the same way.
Controlled#
Hold the zoom yourself with zoom and onZoomChange: here a Slider outside the preview drives it, and the buttons, the menu and a pinch inside it report back. rotation works the same way, and defaultPage with onPageChange opens a PDF at a page and follows the reader through it.
Keyboard and gestures#
The keys work while focus is anywhere in the preview, except in the page field and inside a video player, which keeps its own. The dialog puts focus on the canvas as it opens, and the arrow keys, Page Up/Down and Space scroll it natively.
| +− | Zoom in and out, one step (⌘/Ctrl too, so the browser's own zoom stays out of it) |
| 0 | Fit the page |
| ⌘/CtrlWheel | Zoom around the pointer (a trackpad pinch arrives the same way) |
| R⇧R | Rotate right, rotate left |
| ←→ | Previous and next page, when nothing scrolls sideways |
| F | Full screen |
| Esc | Leave full screen, then close the dialog |
On a touch screen one finger scrolls natively, with its momentum, and two pinch. A photo larger than the canvas drags to pan; a document keeps its drag for selecting text.
PDF setup#
PDF.js runs its parser in a Web Worker. In a Next.js app (Turbopack or webpack) the component starts it from the package on first use, with nothing to configure. With another bundler, or to serve the worker yourself (a CDN, a shared worker), set GlobalWorkerOptions.workerSrc before the first PDF opens and the component leaves it alone. Documents that rely on the standard fonts, on CJK character maps or on JPEG 2000 images render best with PDF.js's assets available: copy them into public/ and pass their folders through pdfOptions.
// cp -r node_modules/pdfjs-dist/{cmaps,standard_fonts,wasm} public/pdfjs/
<FilePreview
src="/files/q3-report.pdf"
pdfOptions={{
cMapUrl: "/pdfjs/cmaps/",
standardFontDataUrl: "/pdfjs/standard_fonts/",
wasmUrl: "/pdfjs/wasm/",
}}
/>// Vite: the worker's URL comes from the bundler, then PDF.js is told where it is.
import { GlobalWorkerOptions } from "pdfjs-dist"
import workerSrc from "pdfjs-dist/build/pdf.worker.min.mjs?url"
GlobalWorkerOptions.workerSrc = workerSrcAPI reference#
FilePreview
The root. Takes the file as file (a File or Blob) or src, with name, type and size to name it when a URL can't, and kind to pick the renderer yourself (pdf · image · text · video · audio · unsupported). The view: zoom / defaultZoom ("fit", "width" or a scale; default "fit") with onZoomChange, rotation / defaultRotation with onRotationChange, defaultPage and onPageChange, minZoom (0.1) and maxZoom (5). Also variant (default · outline · plain), defaultThumbnailsOpen, textLayer (PDF selectable text, on), pdfOptions (passed to PDF.js) and onDownload to replace the built-in download.
FilePreviewHeader · FilePreviewIcon · FilePreviewInfo · FilePreviewTitle · FilePreviewMeta · FilePreviewActions
The identity bar. FilePreviewIcon is the file's FileIcon, FilePreviewTitle its name and FilePreviewMeta a line composed from what is known (format, size, then the page count, the pixel size, the running time or the line count as the file loads). Children replace the title and the meta.
FilePreviewDownload · FilePreviewClose
Ghost Buttons for the header. Download saves the file under its own name (a cross-origin URL is fetched first, since the browser ignores download there); pass children for a labelled button. Close closes a FilePreviewDialog; anywhere else, wire its onClick.
FilePreviewThumbnails
The page rail beside the canvas, from md up, for a document with more than one page. Thumbnails paint as the rail scrolls; the current page wears the brand ring.
FilePreviewViewport
The canvas: renders the file and holds what floats over it. It owns the gestures (wheel, pinch, drag to pan, double-click) and the scroller that takes focus; contentProps reaches that scroller.
FilePreviewToolbar
The bar the controls live in, a Toolbar that takes the Toolbar's props (variant, size, scrollable, on by default). Floats in the viewport, docks in the header.
FilePreviewZoomOut · FilePreviewZoomMenu · FilePreviewZoomIn · FilePreviewFit · FilePreviewRotate · FilePreviewPageNav · FilePreviewThumbnailsToggle · FilePreviewFullscreen
The controls, each a ToolbarButton with its tooltip and shortcut. FilePreviewZoomMenu takes presets, FilePreviewRotate a direction. Each renders nothing when it has nothing to do for the file in hand, and the fullscreen control nothing where the browser can't go full screen.
FilePreviewDialog
The preview in a modal: every FilePreview prop, plus open, defaultOpen, onOpenChange, title (the accessible title, the file name by default) and previewClassName. Built on Radix Dialog with the Dialog's own scrim.
