react-pdf-kit-toolbar-customization
Use this skill when: the developer asks to customize the viewer's
toolbar. Examples: hide one or more built-in tools, add a custom
button, replace an existing tool with a custom component, or split
the layout into a custom top bar and left sidebar. For replacing the
entire layout chrome (full headless), use
react-pdf-kit-custom-layout instead.
The recommended path is RPLayout's toolbar prop with the
RPHorizontalBar (top bar) and RPVerticalBar (left sidebar)
components. Each bar accepts a slots prop that toggles individual
tools on or off or replaces them with your own component. For
projects that need a completely different visual shell, individual
tool exports (ZoomInTool, PrintTool, etc.) can be composed in
your own container.
Gotchas
RPDefaultLayoutis deprecated in v2. UseRPLayoutwith the structuredtoolbarprop. Older examples that passslotsandiconsdirectly toRPDefaultLayoutshould migrate.- The recommended customization API is
RPHorizontalBar/RPVerticalBar, not bypassingRPLayoutentirely. BypassingRPLayoutis reserved for fully custom shells (see step 4). - Each tool is independently exportable. Examples:
ZoomInTool,ZoomOutTool,PrintTool,DownloadTool,RotateClockwiseTool,SwitchViewModeTool,ToggleSidebarTool. Use these only if you are building a fully custom bar (step 4). Do NOT replicate their behavior by hand, or you'll lose the accessibility wiring. - Preserve Radix primitives. Built-in tools wrap Radix tooltips
and buttons. Custom tools should also use Radix or at minimum
preserve
aria-label, focusable buttons, and visible focus rings. - Keyboard navigation must keep working. Tools are reachable via
Taborder. Don't insert non-focusable wrappers (div onClick) where a RadixButtonbelongs. - Use the documented hooks rather than reaching into context
directly. The viewer exposes
useZoomContext,useDocumentContext,usePaginationContext,usePrintContext,useFileDownload, etc. for custom tools to call.
Procedure
1. Default toolbar (no customization)
Drop in RPLayout. The default top bar (zoom, navigation, search,
download, etc.) and left sidebar (thumbnails) render automatically.
import {
RPConfig,
RPProvider,
RPLayout,
RPPages,
} from '@react-pdf-kit/viewer'
export function PdfViewer({ src }: { src: string }) {
return (
<RPConfig>
<RPProvider src={src}>
<RPLayout toolbar>
<RPPages />
</RPLayout>
</RPProvider>
</RPConfig>
)
}
<RPLayout toolbar> (the boolean form) renders the default top bar and
left sidebar. The structured toolbar={{ ... }} form below customizes
them.
2. Partial customization (hide / show individual tools)
Pass RPHorizontalBar (top bar) and RPVerticalBar (left sidebar)
to RPLayout.toolbar. Each bar's slots prop toggles tools by name.
import {
RPConfig,
RPProvider,
RPLayout,
RPPages,
RPHorizontalBar,
RPVerticalBar,
} from '@react-pdf-kit/viewer'
export function PdfViewer({ src }: { src: string }) {
return (
<RPConfig>
<RPProvider src={src}>
<RPLayout
toolbar={{
topbar: {
component: (
<RPHorizontalBar
slots={{
// Hide these tools on the top bar:
searchTool: false,
openFileTool: false,
fullscreenTool: false,
// Everything else stays at its default.
}}
/>
),
},
leftSidebar: { component: <RPVerticalBar /> },
}}
>
<RPPages />
</RPLayout>
</RPProvider>
</RPConfig>
)
}
Set a slot to false to hide it. Set it to a React component to
replace it with your own implementation (see step 3).
To hide the left sidebar entirely, pass an empty fragment:
leftSidebar: { component: <></> }
To use only the top bar with no left sidebar, omit the leftSidebar
key from toolbar.
3. Replace a built-in tool with a custom component
Pass a component to the slot instead of false. The skill below
adds a confirmation prompt before download:
<RPLayout
toolbar={{
topbar: {
component: (
<RPHorizontalBar
slots={{
downloadTool: ({ download }) => (
<button
type="button"
aria-label="Download with confirmation"
=> {
if (confirm('Download this file?')) download()
}}
>
Save
</button>
),
}}
/>
),
},
}}
>
<RPPages />
</RPLayout>
The slot callback receives the same context the default tool would
have used (here, download). Other slots expose similar props
(see the RPHorizontalBar component docs
for the full slot table).
4. Fully custom bar (compose individual tool exports)
When the visual shell needs to differ substantially from the default,
skip RPLayout.toolbar and build your own bar from the individual
*Tool exports. Render this bar yourself, with RPLayout
configured to not render its own toolbar:
import {
RPConfig,
RPProvider,
RPLayout,
RPPages,
ZoomInTool,
ZoomOutTool,
PrintTool,
DownloadTool,
} from '@react-pdf-kit/viewer'
function CustomTopBar() {
return (
<div
role="toolbar"
aria-label="PDF viewer toolbar"
style={{
display: 'flex',
gap: 8,
padding: '8px 16px',
borderBottom: '1px solid var(--rp-border, #e5e7eb)',
}}
>
<ZoomOutTool />
<ZoomInTool />
<span style={{ flex: 1 }} />
<PrintTool />
<DownloadTool />
</div>
)
}
export function PdfViewer({ src }: { src: string }) {
return (
<RPConfig>
<RPProvider src={src}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<CustomTopBar />
<div style={{ flex: 1, minHeight: 0 }}>
<RPLayout toolbar={false}>
<RPPages />
</RPLayout>
</div>
</div>
</RPProvider>
</RPConfig>
)
}
The wrapper around RPLayout MUST set flex: 1; min-height: 0; so
the virtualizer has a concrete viewport to measure. toolbar={false}
tells RPLayout not to render its own bar (your custom one is
already mounted above it).
If you also need to drop the sidebar, prefer
react-pdf-kit-custom-layout instead, which gives you a clean
headless surface for fully bespoke layouts.
5. Add a custom button using a documented hook
For a one-off custom tool (not replacing an existing slot), import the hook the tool needs and render a Radix-wrapped button:
import { useDocumentContext } from '@react-pdf-kit/viewer'
import * as Tooltip from '@radix-ui/react-tooltip'
export function CopyLinkTool() {
const { src } = useDocumentContext()
return (
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
type="button"
aria-label="Copy document link"
=> navigator.clipboard.writeText(String(src))}
>
Copy link
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content sideOffset={6}>Copy link</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
)
}
Drop <CopyLinkTool /> into either an RPHorizontalBar slot
override or your fully custom bar from step 4.
Verify
pnpm install
pnpm build
pnpm dev
Open the page. Tab through the toolbar with the keyboard. Every
button should receive focus, show a visible focus ring, and respond
to Enter. Use a screen reader (VoiceOver, NVDA) to confirm each
button has an accessible name. Open a PDF, scroll, select text. The
viewer should still virtualize and the text layer should still be
selectable.
References
RPLayoutcomponent reference.RPHorizontalBarandRPVerticalBarcomponent references.- Customize Toolbar guide for the full partial / full customization story.
- Radix Tooltip: https://www.radix-ui.com/primitives/docs/components/tooltip.
- Companion skills:
react-pdf-kit-setup: first-time setup.react-pdf-kit-custom-layout: for full headless replacement of the default layout (preferred for substantial UI redesigns).