- PDF Viewer
- PDF Toolbar
- PDF More Actions Menu
- PDF Floating Toolbar
- PDF Document Tabs
- PDF View
- PDF Document Grid
- PDF Document Info
- PDF Page Navigation
- PDF Zoom Controls
- PDF Undo/Redo Buttons
- PDF Keyboard Shortcuts
- PDF Bookmark Sidebar
- PDF Thumbnail Sidebar
- PDF Search Panel
- PDF Attachment List
- PDF Annotation Layer
- PDF Annotation Toolbar
- PDF Annotation Inspector
- PDF Annotation Selection Menu
- PDF Annotation Sidebar
- PDF Comment
- PDF Comment Draft
- PDF Comment Button
- PDF Comment Sidebar
- PDF Redaction Toolbar
- PDF Capture Button
- PDF Form Fill Toggle
- PDF Signature Button
- PDF Stamp Button
Scope a subtree to one view, so multiple panes can read different documents side by side.
"use client";
import { useAllViews } from "@embedpdf/plugin-view-manager/react";About#
<PdfView /> scopes a subtree to a single view — one pane of a multi-pane
viewer. It shadows the ambient view context that <PdfViewer> provides, so a
<PdfToolbar>, <PdfDocumentTabs>, or <PdfViewerContent> inside resolves
that pane's active document rather than the focused pane's. It's built on the
same view-manager that powers document tabs:
- Scope — a pane's chrome reads its own document. Paging or switching a tab in one pane never disturbs the other.
- Focus — pointer-down anywhere in a pane focuses its view and stamps
data-focused. Chrome rendered outside every<PdfView>— a shared sidebar, an app-level toolbar — reads the focused pane, so it follows the pane you're working in.
Two controls move documents between panes:
<PdfMoveToNewPaneButton />creates a pane beside the current one and moves the active document into it. Disabled until the pane holds more than one document, since moving one out is only meaningful while another stays behind.<PdfClosePaneButton />collapses a pane, moving its documents into a sibling rather than orphaning them. Disabled while only one pane exists.
View-manager ships inside <PdfViewer> already, so panes need no extra plugin
registration. A single-pane viewer never needs <PdfView> at all.
Layout is yours#
pdfcn ships no layout component for panes, on purpose. useAllViews() gives you
the views and <PdfView> scopes one; where they go is your call — a
<ResizablePanelGroup>, a CSS grid, a tab strip, a portal into a second browser
window. Baking a resizable group into the component would make one arrangement
the only arrangement, and would put another library's props in pdfcn's public
type.
A document lives in exactly one view. view-manager's add-document reducer removes a document from every other view before adding it, so the same document cannot be open in two panes — hence "move to new pane" rather than "split". Comparing page 3 with page 40 of one document needs upstream support.
Installation#
pnpm dlx shadcn@latest add pdfcn/view
Usage#
Map useAllViews() into your layout of choice, and wrap each pane's chrome in
a <PdfView>.
function Panes() {
const views = useAllViews();
return (
<ResizablePanelGroup>
{views.map((view, index) => (
<React.Fragment key={view.id}>
{index > 0 ? <ResizableHandle withHandle /> : null}
<ResizablePanel id={view.id} className="min-w-0">
<PdfView viewId={view.id}>
<PdfDocumentTabs />
<PdfToolbar>
<PdfPageNavigation />
<PdfZoomControls />
<div className="ml-auto flex items-center gap-1">
<PdfMoveToNewPaneButton />
<PdfClosePaneButton />
</div>
</PdfToolbar>
<PdfViewerContent />
</PdfView>
</ResizablePanel>
</React.Fragment>
))}
</ResizablePanelGroup>
);
}Render it inside <PdfViewer>. The map needs a child component, since
useAllViews() reads the plugin registry and <PdfViewer> mounts its
children only once the engine is ready.
<PdfViewer
documents={[{ url: "/sample.pdf" }, { url: "/form.pdf" }]}
className="h-[720px]"
>
<Panes />
</PdfViewer>Examples#
Default#
Two documents load in one pane. Moving one out opens a second pane; each pane's toolbar and tabs act only on its own document. Drag the handle to resize, and close a pane to merge its documents back.
"use client";
import { useAllViews } from "@embedpdf/plugin-view-manager/react";Shared sidebar#
Chrome outside every <PdfView> reads the focused pane, which is how one
sidebar can serve every pane — click into a pane and the sidebar follows. Put
the same sidebar inside a <PdfView> instead and it pins to that pane.
That pairing is the whole implementation of a pin: hold the pinned view's id in
state, and wrap the sidebar's body in a <PdfView> only while it's set.
const [pinnedViewId, setPinnedViewId] = React.useState<string | null>(null);
return pinnedViewId ? <PdfView viewId={pinnedViewId}>{body}</PdfView> : body;Read the pin as live only while its view is (views.some((view) => view.id === pinnedViewId)) and closing the pinned pane reverts the sidebar to following
focus on its own, with no effect to keep the two in step.
<PdfWorkspaceApp /> ships both halves — a shared
sidebar with a pin toggle in its header.
Accessibility#
The two controls are single buttons labelled "Move to new pane" and "Close pane", each with a matching tooltip, and each disabled rather than hidden while it can't act — so the toolbar never reflows under the pointer.
Focus follows pointer-down, not keyboard focus. When panes are arranged in a resizable group, the drag handle is keyboard operable: focus it and use the arrow keys to resize.
API Reference#
PdfView#
A <div /> carrying data-slot="pdf-view" and data-focused. Accepts every
prop a div takes, plus:
| Prop | Type | Description |
|---|---|---|
viewId | string | The view-manager view this subtree is scoped to. |
PdfMoveToNewPaneButton#
A <Button /> carrying data-slot="pdf-move-to-new-pane-button". Creates a new
pane and moves the current pane's active document into it. Disabled until the
pane holds more than one document. Accepts every prop the button takes.
PdfClosePaneButton#
A <Button /> carrying data-slot="pdf-close-pane-button". Merges the current
pane's documents into a sibling and removes the pane. Disabled while only one
pane exists. Accepts every prop the button takes.