- 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
"use client";
import { createPluginRegistration } from "@embedpdf/core";About#
<PdfCaptureButton /> arms marquee capture. Dragging a box on the page is what actually produces the image — the button only decides whether that drag captures or scrolls.
The plugin emits a Blob and stops there; it has no opinion about where the image goes. This component fills that in, downloading the capture by default and handing it to you instead when you pass onCapture.
Capture is a heavy-tier plugin, so <PdfViewer /> does not register it for you. The plugin and the marquee layer both have to be added — see Usage.
The button renders nothing at all when the capture plugin isn't registered. If it's missing, that's the registration rather than a missing document.
Installation#
pnpm dlx shadcn@latest add pdfcn/capture-button
Usage#
Register the capture plugin.
import { createPluginRegistration } from "@embedpdf/core";
import { CapturePluginPackage } from "@embedpdf/plugin-capture/react";
// Outside the component — a new array identity on every render tears the
// engine down and rebuilds it, losing scroll and zoom state.
const plugins = [
createPluginRegistration(CapturePluginPackage, {
scale: 2,
imageType: "image/png",
}),
];scale is the resolution the region is rendered at, independent of the zoom on screen — 2 captures at twice the page's natural size, which is what keeps a capture legible when it's pasted somewhere else. imageType accepts image/png, image/jpeg, image/webp, or image/bmp, and also decides the downloaded file's extension. See embedpdf's Capture plugin for its full configuration.
Render the marquee layer and the button.
<PdfViewer
documents={[{ url: "/sample.pdf" }]}
plugins={plugins}
className="h-[720px]"
>
<PdfToolbar>
<PdfCaptureButton />
</PdfToolbar>
<PdfViewerContent
pageLayers={({ documentId, pageIndex }) => (
<MarqueeCapture documentId={documentId} pageIndex={pageIndex} />
)}
/>
</PdfViewer>Without <MarqueeCapture /> the drag still captures, but nothing draws while the pointer moves — so the user is selecting a region they can't see. Treat the layer as required, not decorative.
Examples#
Default#
"use client";
import { createPluginRegistration } from "@embedpdf/core";Arm the button, drag a box over the page, and the capture downloads as a PNG.
Handling the capture yourself#
Pass onCapture to replace the download — the image goes wherever you send it instead:
<PdfCaptureButton
onCapture={async ({ blob, pageIndex, rect }) => {
const body = new FormData();
body.append("image", blob, `page-${pageIndex + 1}.png`);
await fetch("/api/captures", { method: "POST", body });
}}
/>The event also carries rect (the captured region in page coordinates), documentId, scale, imageType, and withAnnotations, so a capture can be stored alongside where it came from.
Copying to the clipboard is the other common destination:
<PdfCaptureButton
onCapture={({ blob }) => {
void navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
}}
/>Clipboard writes need a secure context, and Safari only accepts image/png — pick your imageType accordingly.
When copying is disabled#
Capturing a region copies content out of the document, so the button reads the active document's copy-contents permission and disables itself — with a tooltip explaining why — when the flag is denied. The demo below forces the flag off with a permissions override; a real document carries it in its own flags. See Permissions for the full model.
"use client";
import { createPluginRegistration } from "@embedpdf/core";Accessibility#
The button is a <Toggle />, so whether capture is armed is exposed through its pressed state rather than colour alone, and it's reachable and operable from the keyboard.
Capturing a region is pointer-only — arming works from the keyboard, but dragging the marquee has no keyboard equivalent.
The default download is a silent side effect: nothing on screen changes when a capture succeeds. If capture is a significant part of your interface, pass onCapture and announce the result yourself.
API Reference#
PdfCaptureButton#
A <Toggle /> carrying data-slot="pdf-capture-button", bound to whether marquee capture is armed. Accepts every <Toggle /> prop.
| Prop | Type | Default | Description |
|---|---|---|---|
onCapture | (event: CaptureAreaEvent) => void | Downloads the image. | Called with each captured region. |
Returns null when the document isn't loaded or the capture plugin isn't registered.
onCapture is read through a ref, so passing an inline function is safe — it won't resubscribe mid-drag or drop a capture in flight.