GitHub

Header

FileViewerHeader composition for file identity, passive metadata, and renderer-registered controls.

FileViewerHeader is the file row. It should answer three questions in one line: what file is open, what passive facts matter, and what actions the active renderer exposes.

The default header renders title, metadata, and renderer controls. Add a FileViewerSidebarTrigger when FileViewerContent contains a sidebar.

Installation

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

Example

The header stays file-type agnostic. The active renderer registers controls into FileViewerControls, while FileViewerTitle owns the file name and FileViewerMeta owns passive metadata.

spacex-prospectus.pdf

Usage

The compact default composition is enough when there is no sidebar:

import {
  FileViewer,
  FileViewerContent,
  FileViewerDocument,
  FileViewerHeader,
  FileViewerMeta,
  FileViewerTitle,
  FileViewerProvider,
  FileViewerInset,
  FileViewerControls,
  FileViewerViewport,
} from "@/components/ui/file-viewer";
 
export function Example() {
  return (
    <FileViewerProvider source={source}>
      <FileViewer>
        <FileViewerHeader>
          <div className="flex min-w-0 flex-1 items-baseline gap-2">
            <FileViewerTitle />
            <FileViewerMeta />
          </div>
          <FileViewerControls />
        </FileViewerHeader>
        <FileViewerContent>
          <FileViewerInset>
            <FileViewerViewport>
              <FileViewerDocument />
            </FileViewerViewport>
          </FileViewerInset>
        </FileViewerContent>
      </FileViewer>
    </FileViewerProvider>
  );
}

When FileViewerContent contains FileViewerSidebar, place the trigger before the title:

<FileViewerProvider source={source} defaultSidebarOpen>
  <FileViewer>
    <FileViewerHeader>
      <FileViewerSidebarTrigger />
      <div className="flex min-w-0 flex-1 items-baseline gap-2">
        <FileViewerTitle />
        <FileViewerMeta />
      </div>
      <FileViewerControls />
    </FileViewerHeader>
    <FileViewerContent>
      <FileViewerSidebar aria-label="Document sections" width="18rem" />
      <FileViewerInset>
        <FileViewerViewport>
          <FileViewerDocument />
        </FileViewerViewport>
      </FileViewerInset>
    </FileViewerContent>
  </FileViewer>
</FileViewerProvider>

FileViewerHeader can also be rendered without children. In that case it uses the default title/meta/controls composition:

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

Parts

The header is composed from small context-aware parts. None of them take a source prop; each reads what it needs from the file viewer state or from the surrounding viewer frame.

ComponentRole
FileViewerTitleFile name.
FileViewerMetaOptional passive file metadata.
FileViewerControlsRenderer-registered operational controls.
FileViewerSidebarTriggerSidebar toggle; documented under Navigation.

FileViewerTitle

Renders the current file display name.

<FileViewerTitle />
  • Reads descriptor.displayName from file-viewer context. It takes no source or name prop of its own.
  • Renders a data-slot="file-viewer-title" element with truncating labels, so long file names clip cleanly.
  • Do not add a decorative file icon beside the title. The file name carries the identity; metadata and controls provide the surrounding context.

FileViewerMeta

Renders passive file facts such as MIME type or resolved category.

<FileViewerMeta />
  • Reads MIME type and category from file-viewer context.
  • Renders a data-slot="file-viewer-meta" element and returns null when no metadata is available.
  • Use visibility="visible" when metadata should stay visible on small screens.

FileViewerControls

Renders operational controls registered by the active renderer: page position, zoom, rotate, retry, copy, and download.

<FileViewerControls />

Add workflow actions beside the renderer controls with extra:

<FileViewerControls extra={<ApproveButton />} />
  • Reads the registered control set (position, zoom, rotate, loading, extra) from file-viewer context. The header takes no PDF- or spreadsheet-specific props — renderers register their controls upward.
  • Download defaults to the file's original download action. A renderer can register its own downloads (and registering an empty list hides the download button entirely).
  • Renders a data-slot="file-viewer-controls" row, right-aligned in the header.
  • The extra prop is merged with any renderer-registered extras, so workflow actions can sit beside the renderer's own controls.

FileViewerSidebarTrigger

Toggles the nearest FileViewerSidebar. Place it before the title when FileViewerContent contains a sidebar.

<FileViewerSidebarTrigger />

The header does not know whether the document is a PDF, spreadsheet, image, or email. Renderer-specific controls register into the file viewer state and appear through FileViewerControls.

Layout Rule

Keep title and metadata on the left, controls on the right:

<FileViewerHeader>
  <div className="flex min-w-0 flex-1 items-baseline gap-2">
    <FileViewerTitle />
    <FileViewerMeta />
  </div>
  <FileViewerControls />
</FileViewerHeader>

Avoid file-type icons in the title row. The file name is the primary identity; metadata and controls are enough.

API Reference

ComponentProps
FileViewerHeaderReact.ComponentProps<typeof ViewerHeader>
FileViewerTitleReact.ComponentProps<"span">
FileViewerMetaReact.ComponentProps<"span">
FileViewerControlsViewerControls props except registered controls
FileViewerSidebarTriggerViewerSidebarTrigger props

FileViewerControls receives renderer-registered position, zoom, rotate, downloads, loading, and extra from file-viewer state. Pass extra for workflow actions that should sit beside renderer controls.

Source

file-viewer.tsx