GitHub

Overview

One viewer for everything — detects the file type and renders the right component, from PDFs and spreadsheets to images, code, Markdown, and HTML.

The File Viewer is a single entry point for previewing any file. It detects the type from the file name (then the MIME type) and lazy-loads only the matching viewer, so the heavy parser for a format you never open is never downloaded.

It is the flagship document component. The format renderers are not peers of File Viewer; they are routes inside it: PDF, DOCX, Image, PPTX, XLSX, and CSV. File Viewer also owns inline, client-side rendering for text, code, JSON, Markdown, and HTML.

spacex-prospectus.pdf

Installation

pnpm dlx shadcn@latest add @retab/file-viewer

This pulls in the format-specific viewers it delegates to, plus the Markdown Viewer for Markdown documents, custom fixed-line virtualization for the Code Viewer, and @chenglou/pretext for the wrapped Text and Markdown surfaces.

Usage

import { FileViewerPreview } from "@/components/ui/file-viewer";
 
export function Example() {
  return (
    <FileViewerPreview
      source={{ kind: "url", url: "/report.pdf", fileName: "report.pdf" }}
    />
  );
}

source.fileName drives type detection and the download button. If you only have a URL, the URL is used for display and its basename is used for downloads; pass source.mimeType when the extension is missing or ambiguous, or category to force a category.

Composition Boundaries

FileViewerProvider is the file router and file-scoped provider stack. FileViewer is the frame beneath it. Do not use either one to own workflow semantics. Do use the named anatomy when a file-backed surface needs sidebars, composed panes, or file-scoped navigation.

Use the file viewer parts when a workflow needs one file header above domain chrome:

<FileViewerProvider source={source} defaultSidebarOpen>
  <FileViewer>
    <FileViewerHeader>
      <FileViewerSidebarTrigger />
      <FileViewerTitle />
      <FileViewerControls />
    </FileViewerHeader>
    <FileViewerContent>
      <FileViewerSidebar aria-label="Document sections" />
      <FileViewerInset>
        <FileViewerViewport>
          <FileViewerDocument />
        </FileViewerViewport>
      </FileViewerInset>
    </FileViewerContent>
  </FileViewer>
</FileViewerProvider>

FileViewerTitle answers what file this is, and FileViewerControls renders operational controls registered by the active file renderer. FileViewerDocument is the routed file renderer.

See Anatomy for the compound API and Sidebar for file-scoped navigation.

File Viewer structure
File Viewer sidebar ownership

Use the preview shell for the common case:

<FileViewerPreview source={source} />

Use the composed shell when the file surface needs file-scoped sidebars or custom header composition:

<FileViewerProvider source={source}>
  <FileViewer>
    <FileViewerHeader>
      <FileViewerTitle />
      <FileViewerControls />
    </FileViewerHeader>
    <FileViewerContent>
      <FileViewerInset>
        <FileViewerViewport>
          <FileViewerDocument />
        </FileViewerViewport>
      </FileViewerInset>
    </FileViewerContent>
  </FileViewer>
</FileViewerProvider>

Use FileViewerPreview for standalone nested leaf previews where the parent already owns the surrounding surface:

<FileViewerPreview source={attachment.source} />

Supported types

CategoryExtensionsRendered by
PDFpdfPDF Viewer
WorddocxDOCX Viewer
Spreadsheetxlsx, xls, xlsmXLSX Viewer
PresentationpptxPPTX Viewer
Tabularcsv, tsvCSV Viewer
Imagepng, jpg, jpeg, gif, webp, avif, svg, bmp, ico, tif, tiffImage Viewer
Markdownmd, markdown, text/markdownMarkdown Viewer
HTMLhtml, htmsandboxed iframe
Texttxt, textPretext Text Viewer
Code & logslog, json, xml, yaml, mdx, ts, py, go, …Code Viewer

Anything unrecognized falls back to a download card.

The Email Viewer also belongs to the File Viewer category, but it is a compound MIME viewer rather than a routed FileViewerDocument renderer: its body and attachment leaves render through File Viewer primitives.

How it performs

  • Code-split delegation. Each format-specific viewer is a React.lazy import — opening a PDF never loads SheetJS, pptxviewjs, or the others.
  • Code and text split. Code-like files render as fixed lines through the Code Viewer with line numbers. Prose text renders through the Pretext Text Viewer with natural wrapping and custom variable-height virtualization.
  • Markdown has a dedicated continuous document path. Markdown URL, Blob, inline text, and MIME-only sources route to MarkdownViewer, which keeps Pretext-informed virtual chunks as an internal layout strategy while React/GFM owns the visible document rendering. The Text Viewer remains for prose text that is not Markdown.
  • Safe HTML. Standalone .html files render inside a <iframe sandbox> with no script execution and no same-origin access, so untrusted documents preview without running code or reaching the app.

API Reference

PropOwnerDescription
sourceFileViewerProviderCanonical file source. URL, Blob, and text sources carry file metadata with the payload identity.
categoryFileViewerProviderForce a category, bypassing detection.
defaultSidebarOpenFileViewerProviderInitial sidebar open state for composed viewers.
sidebarModeFileViewer"auto", "inline", or "overlay" sidebar layout mode.
sideFileViewerSidebarWhich side the sidebar mounts on.
classNameFileViewerOptional class on the viewer root element.

The detectCategory(fileName, mimeType?) helper is also exported if you need to branch on the resolved category yourself.

Source

file-viewer.tsx