0

PDF Document Tabs

A browser-style tab strip for switching between, closing, and opening documents.

"use client";

import { PdfDocumentTabs } from "@/components/pdf-document-tabs";

About

<PdfDocumentTabs /> is the front end for embedpdf's view-manager. A viewer can load many documents, but view-manager decides which one a view shows — and this strip is how a person drives that:

  • Switch — clicking a tab sets the view's active document, so the toolbar, zoom, search, and content all follow to the new document.
  • Close — the tab's close button closes the file through the document manager (closeDocument), not just detaches it from the view, matching how embedpdf's own tab bar behaves. The view falls back to a sibling document.
  • Open — the trailing + opens another document, which lands as a new tab.

The strip binds to the current view — the focused view by default, or the enclosing <PdfView>'s view inside a split layout — so the same component works as a top-level tab bar or per-pane.

Anything you pass as children lands at the strip's trailing edge. That's the slot for actions that belong to the group of tabs rather than to the document one of them selects — splitting the pane, closing it, an overflow menu — which is where VS Code puts the same kind of control.

Installation

pnpm dlx shadcn@latest add pdfcn/document-tabs

Usage

Load more than one document and render the strip above the content.

<PdfViewer
  documents={[{ url: "/sample.pdf" }, { url: "/form.pdf" }]}
  className="h-[720px]"
>
  <PdfDocumentTabs />
  <PdfToolbar>
    <PdfPageNavigation />
    <PdfZoomControls />
  </PdfToolbar>
  <PdfViewerContent />
</PdfViewer>

Examples

Default

Two documents load as tabs. Click between them and the toolbar acts on whichever is showing; close one and the view falls back to its sibling; use + to open a third.

"use client";

import { PdfDocumentTabs } from "@/components/pdf-document-tabs";

Pane actions

In a split layout each pane owns a strip, so the strip is also where that pane's own controls belong. Children sit at the trailing edge, past the tabs and the +:

<PdfDocumentTabs>
  <PdfMoveToNewPaneButton />
  <PdfClosePaneButton />
  <PdfToolbarSeparator />
  <PdfMoreActionsMenu />
</PdfDocumentTabs>

Keep the toolbar below for controls that act on the document — zoom, page navigation, search. The split leaves the toolbar its whole row for the controls that shed under a container query, and it matches how people already read the two rows: tabs and their actions above, document and its actions below. The workspace app block wires it up this way.

Accessibility

The strip is a Base UI Tabs — the same primitive ui/tabs wraps — so it gets the full tab contract rather than an approximation of it. One tab holds the tab stop and the arrow keys move between them, looping at the ends.

Focus and activation are separate: arrowing onto a tab focuses it without switching documents, and Enter or Space commits. That's the deliberate choice for this strip — activating on focus would render a different PDF on every keypress as you arrowed past.

Each tab's close button is labelled Close {name} and follows the same roving rule as the tabs, so only the open document's × is a tab stop; ten open documents cost one, not ten. The trailing button is labelled "Open another PDF" and sits outside the tab list, since a tab list may only contain tabs.

API Reference

PdfDocumentTabs

The Tabs.Root, carrying data-slot="pdf-document-tabs" — controlled off the view, so the view-manager stays the one source of truth for which document is showing. Accepts every prop a <div> takes, plus:

PropTypeDefaultDescription
hideSinglebooleanfalseHide the whole strip while only one document is open.
showAddButtonbooleantrueRender the trailing button that opens another document.
childrenReact.ReactNodeControls pinned to the strip's trailing edge — pane actions, an overflow menu.

The strip reads the current view through usePdfView() and the view-manager capability, and the open documents through useOpenDocuments(). Rendered inside a <PdfView>, it scopes to that pane's view instead of the focused one.

Each tab is a cell carrying data-slot="pdf-document-tab", with data-active when it's the one showing; its close button carries data-slot="pdf-document-tab-close", the trailing opener carries data-slot="pdf-document-tabs-add", and children are wrapped in data-slot="pdf-document-tabs-actions". Closing calls the document manager's closeDocument — it closes the file rather than merely detaching it from the view — and which document takes over afterwards is left to the view-manager.