A banner that appears only while a document carries unapplied redaction marks, and the one place applying them happens.
"use client";
import { createPluginRegistration } from "@embedpdf/core";About#
A /Redact annotation (ISO 32000-1 §12.5.6.23) is a proposal. It marks a region and says what should replace it, and nothing is removed until a separate apply pass rewrites the page's content stream. So a marked document is unfinished, and the danger is someone sending it on believing the marks did the work.
<PdfRedactionBar /> is the answer to that. It counts the pending marks, carries Clear and Apply, and mounts and unmounts with the marks themselves: nothing pending, nothing on screen. It arrives on its own rather than waiting in a panel to be found.
It's deliberately not a list. In annotation mode the plugin's pending set is a projection over the annotation store — every /Redact in it — so the marks are already named and listed in <PdfAnnotationSidebar />. The bar reports the count and lets the host route there rather than duplicating that list.
Redaction is a heavy-tier plugin, so <PdfViewer /> does not register it for you — see Usage.
Applying is irreversible and takes effect on the loaded document immediately. Keep the original file if you need one — this component gives you no way back.
Installation#
pnpm dlx shadcn@latest add @pdfviewcn/redaction-bar
Usage#
Register the annotation and redaction plugins.
import { createPluginRegistration } from "@embedpdf/core";
import { AnnotationPluginPackage } from "@embedpdf/plugin-annotation/react";
import { RedactionPluginPackage } from "@embedpdf/plugin-redaction/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(AnnotationPluginPackage),
createPluginRegistration(RedactionPluginPackage, {
drawBlackBoxes: true,
useAnnotationMode: true,
}),
];Mount the bar where a banner belongs, and give the toolbar the marking modes.
<PdfViewer documents={[{ url: "/sample.pdf" }]} plugins={plugins}>
<PdfToolbar>
<PdfRedactionModes />
</PdfToolbar>
<PdfRedactionBar />
<PdfViewerContent
pageLayers={({ documentId, pageIndex }) => (
<>
<PdfAnnotationLayer documentId={documentId} pageIndex={pageIndex} />
<RedactionLayer documentId={documentId} pageIndex={pageIndex} />
</>
)}
/>
</PdfViewer><PdfRedactionModes /> is the marking half of <PdfRedactionToolbar /> on its own. Compose that rather than the whole toolbar when the bar is mounted: the toolbar carries an Apply of its own, and the irreversible action shouldn't be offered twice.
Put the bar full-bleed under the toolbar and above the content — it renders with no top or side borders so it reads as a property of the document rather than a card floating over it.
Structure#
<PdfRedactionBar>{/* host actions, before Clear and Apply */}</PdfRedactionBar>Anything passed as children lands before the actions — that's where a host puts its own route into the marks. <PdfRedactionActions /> is appended after it, and is exported separately if you want the pair somewhere else entirely.
Examples#
Default#
"use client";
import { createPluginRegistration } from "@embedpdf/core";Routing to the marks#
The count says how many, not which. Since applying is the one action with no way back, give the bar a way into the list — in annotation mode that's the annotation sidebar, where every /Redact already appears under its own name:
<PdfRedactionBar>
<Button
variant="ghost"
size="sm"
onClick={() => {
setTab("annotations");
setPanelOpen(true);
}}
>
Review
</Button>
</PdfRedactionBar>This is what <PdfAnnotatorApp /> does. In legacy storage mode marks aren't annotations and don't appear in the sidebar at all, so point the button at <PdfRedactionPanel /> instead — see Where marks are stored.
Never let a filter or a collapsed panel hide a mark that's about to be destroyed. The bar counts every pending mark in the document regardless of what the list beside it is currently showing.
The actions on their own#
<PdfRedactionActions /> is the Clear/Apply pair, including the confirmation dialog and its copy:
<PdfRedactionActions />Use it directly when you have your own banner, a footer, or a menu to put the pair in. It renders both buttons at all times, disabled while nothing is pending — unlike the bar, which isn't there at all.
When editing is disabled#
Applying rewrites the page contents, so both actions read the active document's modify-contents permission and disable themselves when the flag is denied. The bar still appears — marks still exist and still need reporting — and anchors a tooltip explaining why nothing responds. See Permissions for the full model.
Accessibility#
The bar overrides <Alert />'s default to role="status". Its content changes with every mark made, and alert would interrupt a screen reader on each one; status announces the running count politely.
Applying is behind an <AlertDialog />, so a destructive action can't be triggered by a stray keypress on a focused button. The dialog's description states what is lost, not just that the action is permanent.
Because the bar mounts only while marks are pending, its buttons enter and leave the tab order with it. Nothing else in the surface shifts focus when it appears.
API Reference#
PdfRedactionBar#
An <Alert /> carrying data-slot="pdf-redaction-bar", holding the count, any children, and <PdfRedactionActions />. Accepts every <Alert /> prop.
Returns null when the document isn't loaded, the redaction plugin isn't registered, or nothing is pending.
| Prop | Type | Default | Description |
|---|---|---|---|
documentId | string | null | ambient document | The document to report on. Defaults to the focused view's active document. |
children | ReactNode | — | Rendered before the actions — where a host puts its own route into the marks. |
PdfRedactionActions#
A div carrying data-slot="pdf-redaction-actions", holding Clear and Apply. Accepts every div prop, plus the same documentId.
Returns null when the document isn't loaded or the redaction plugin isn't registered.
| Slot | Element |
|---|---|
pdf-redaction-bar | The banner. |
pdf-redaction-actions | The action pair. |
pdf-redaction-clear | Discards every pending mark. |
pdf-redaction-apply | Opens the confirmation; shows the count. |
Both actions are disabled while nothing is pending, while editing is disallowed, and while an apply is in flight — applying a long document rewrites every affected page, so Apply shows a spinner rather than appearing to do nothing.
There's no per-mark apply — redaction is reviewed as a set and applied as a set. See What isn't here.