File Preview
ProLook 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.
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Anatomy#
import {
FilePreview,
FilePreviewHeader,
FilePreviewIcon,
FilePreviewInfo,
FilePreviewTitle,
FilePreviewMeta,
FilePreviewActions,
FilePreviewDownload,
FilePreviewClose,
FilePreviewThumbnails,
FilePreviewViewport,
FilePreviewToolbar,
FilePreviewThumbnailsToggle,
FilePreviewPageNav,
FilePreviewZoomOut,
FilePreviewZoomMenu,
FilePreviewZoomIn,
FilePreviewFit,
FilePreviewRotate,
FilePreviewFullscreen,
} from "@/components/ui/file-preview"
<FilePreview>
<FilePreviewHeader>
<FilePreviewIcon />
<FilePreviewInfo>
<FilePreviewTitle />
<FilePreviewMeta />
</FilePreviewInfo>
<FilePreviewActions>
<FilePreviewDownload />
<FilePreviewClose />
</FilePreviewActions>
</FilePreviewHeader>
<FilePreviewThumbnails />
<FilePreviewViewport>
<FilePreviewToolbar>
<FilePreviewThumbnailsToggle />
<FilePreviewPageNav />
<FilePreviewZoomOut />
<FilePreviewZoomMenu />
<FilePreviewZoomIn />
<FilePreviewFit />
<FilePreviewRotate />
<FilePreviewFullscreen />
</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.
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
API reference#
FilePreview
The root: resolves the file (a Blob becomes an object URL), picks the renderer, owns the view state and handles the keyboard shortcuts. Forwards native <div> props except onError; every part that renders an element accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| file | Blob | null | - | The file to show: a File from an input or a drop, or any Blob. |
| src | string | - | Or a URL to read it from. Ignored when file is set. |
| name | string | - | Display name. Defaults to the File's name, else the last segment of src. |
| type | string | - | MIME type. Defaults to the File's type; otherwise the name's extension decides. |
| size | number | - | Size in bytes, for the meta line. Defaults to the File's size. |
| kind | "pdf" | "image" | "video" | "audio" | "text" | "unsupported" | - | Pick the renderer yourself instead of detecting it from the type and the name. |
| zoom | FilePreviewZoom | - | Controlled zoom: "fit", "width", or a scale (1 is 100%). |
| defaultZoom | FilePreviewZoom | "fit" | Initial zoom when uncontrolled. |
| onZoomChange | (zoom: FilePreviewZoom) => void | - | Called when the zoom changes, from a control, a key or a pinch. |
| rotation | number | - | Controlled rotation in degrees, in multiples of 90. |
| defaultRotation | number | 0 | Initial rotation when uncontrolled. |
| onRotationChange | (rotation: number) => void | - | Called when the view rotates. |
| defaultPage | number | 1 | The page a PDF opens at. |
| onPageChange | (page: number) => void | - | Called as the reader moves through the pages. |
| minZoom | number | 0.1 | Lowest scale the zoom controls reach (a fit may go lower). |
| maxZoom | number | 5 | Highest scale the zoom controls reach. |
| defaultThumbnailsOpen | boolean | true | Whether the page rail starts open (it only exists from md up). |
| textLayer | boolean | true | Lay PDF.js's selectable text over each page, so text can be selected, copied and found. |
| pdfOptions | FilePreviewPdfOptions | - | Passed to PDF.js's getDocument: cMapUrl, standardFontDataUrl, wasmUrl, password, withCredentials. |
| onDownload | () => void | - | Replace the built-in download (an anchor with the file's name; a cross-origin URL is fetched first). |
| variant | "default" | "outline" | "plain" | "default" | The frame: the edge ring with the smallest lift, the bare --border ring, or no frame at all. |
| Data attribute | Description |
|---|---|
| data-kind | "pdf", "image", "video", "audio", "text" or "unsupported". |
| data-status | "loading", "ready" or "error". |
| data-fullscreen | Present while the preview is full screen. |
FilePreviewHeader
The identity bar across the top, ruled off from the canvas. Forwards all native <div> props.
FilePreviewIcon
The file's FileIcon, its type and extension taken from the file in hand. Forwards FileIcon's props (except type and extension) and native <svg> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "xl" | "sm" | Height: 32, 40, 48 or 64px. |
| variant | "default" | "soft" | "solid" | "default" | FileIcon's fill treatment. |
| label | string | - | Override the stamped text outright. |
FilePreviewInfo
The name-over-meta column; it lets a long name truncate. Forwards all native <div> props.
FilePreviewTitle
The file's name on one truncated line, the whole name on hover. Children replace it. Forwards all native <div> props.
FilePreviewMeta
A line composed from what is known (format, size, then the page count, pixel size, running time or line count as the file loads). Children replace it. Forwards all native <div> props.
FilePreviewActions
The trailing actions on the header's right edge. Forwards all native <div> props.
FilePreviewDownload
Saves the file under its own name; icon-only unless you pass children. A Button: takes every Button prop except iconOnly, and preventDefault() in onClick skips the download.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | ButtonProps["variant"] | "ghost" | Any Button variant. |
| size | ButtonProps["size"] | "sm" | Any Button size. |
| static | boolean | true | Kept for compatibility; controls no longer scale on press. |
FilePreviewClose
An icon-only close button. Inside FilePreviewDialog it closes the dialog; anywhere else wire its onClick. Takes every Button prop except iconOnly.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | ButtonProps["variant"] | "ghost" | Any Button variant. |
| size | ButtonProps["size"] | "sm" | Any Button size. |
| static | boolean | true | Kept for compatibility; controls no longer scale on press. |
FilePreviewThumbnails
The page rail beside the canvas, from md up, for a document with more than one page; renders nothing otherwise. Thumbnails paint as the rail scrolls. Forwards all native <nav> props.
| Data attribute | Description |
|---|---|
| data-state | "open" or "closed" (hidden), toggled by FilePreviewThumbnailsToggle. |
| data-active | On the current page's thumbnail, which wears the ink ring. |
| data-page-number | On each thumbnail, its 1-based page number. |
FilePreviewViewport
The canvas: renders the file in its own focusable scroller and holds what floats over it, the toolbar first. It owns the gestures (⌘/Ctrl + wheel, pinch, drag to pan a zoomed photo, double-click). Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| contentProps | Omit<React.ComponentProps<"div">, "ref" | "children"> | - | Props for the scroller inside, the element that takes focus and scrolls. |
| Data attribute | Description |
|---|---|
| data-pannable | On the scroller while a photo is larger than the canvas and drags to pan. |
| CSS variable | Description |
|---|---|
| --surface | Set to var(--muted), read by controls inside the canvas. |
| --file-preview-toolbar-space | Room reserved at the bottom of the scroll area while a toolbar floats over it. |
FilePreviewToolbar
The controls' bar, a Toolbar that takes every Toolbar prop. Floats bottom centre inside the viewport, docks in the header with variant="plain", and renders nothing for a file it cannot control or one that failed to load.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "floating" | "outline" | "plain" | "solid" | "floating" | The Toolbar's surface. |
| size | "sm" | "md" | "lg" | "md" | The Toolbar's control size. |
| scrollable | boolean | true | Scroll sideways instead of overflowing a narrow canvas. |
FilePreviewZoomOut
One zoom step out (shortcut -). Each control is a ToolbarButton held still on press and takes its props except tooltip and shortcut; it renders nothing when the file cannot zoom.
FilePreviewZoomIn
One zoom step in (shortcut +), disabled at maxZoom. Takes ToolbarButton props except tooltip and shortcut.
FilePreviewZoomMenu
The live zoom readout, which opens fit to page, fit to width and the fixed levels in a dropdown. Takes ToolbarButton props except tooltip, shortcut and children.
| Prop | Type | Default | Description |
|---|---|---|---|
| presets | number[] | ZOOM_PRESETS | The fixed levels listed under the fit modes (1 is 100%): [0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4], filtered to minZoom/maxZoom. |
FilePreviewFit
Fit to page (shortcut 0) and fit to width as a pair of toggles; neither is pressed after a manual zoom.
| Prop | Type | Default | Description |
|---|---|---|---|
| disabled | boolean | false | Disable both toggles. |
FilePreviewRotate
A quarter turn of the view (shortcut R, or ⇧R counterclockwise); the download still saves the original. Takes ToolbarButton props except tooltip and shortcut.
| Prop | Type | Default | Description |
|---|---|---|---|
| direction | "clockwise" | "counterclockwise" | "clockwise" | Which way the view turns. |
FilePreviewPageNav
Previous page, a page field that jumps on Enter (Escape restores it), the count, next page. Renders only for a paged document; takes only className.
FilePreviewThumbnailsToggle
Shows or hides the page rail; renders only where the rail can exist. Takes ToolbarButton props except tooltip and shortcut.
FilePreviewFullscreen
The whole preview full screen (shortcut F); renders nothing where the browser cannot go full screen. Takes ToolbarButton props except tooltip and shortcut.
FilePreviewDialog
The preview in a modal on Radix Dialog with the Dialog's scrim: edge to edge on a phone, focus on the canvas, and it keeps the last file while fading out. Takes every FilePreview prop (children are the preview's parts); the inner preview defaults to variant="plain".
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | - | Controlled open state. |
| defaultOpen | boolean | false | Initial open state when uncontrolled. |
| onOpenChange | (open: boolean) => void | - | Called when the dialog opens or closes (Esc, the scrim, FilePreviewClose). |
| title | string | the file's name | The dialog's visually hidden accessible title. |
| className | string | - | Classes for the dialog surface. |
| previewClassName | string | - | Classes for the FilePreview inside. |
| Data attribute | Description |
|---|---|
| data-state | "open" or "closed", on the dialog surface and the overlay. |
| CSS variable | Description |
|---|---|
| --surface | Set to var(--popover) on the dialog surface. |
