- 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
A tool pill that floats over the page canvas instead of docking above it, pinned to any edge.
"use client";
import { createPluginRegistration } from "@embedpdf/core";About#
<PdfFloatingToolbar /> is the design-tool shape of a toolbar: the document is the interface, and the controls hover on top of it. It's a sibling of <PdfToolbar />, not a variant of it.
The difference is a layout contract, not a look. <PdfToolbar /> sits in the layout's flow, spans the full width, and pushes the canvas down. This one is taken out of flow and pinned to an edge, so it owns a placement — side and align — that a docked bar has no use for, and costs the canvas no height.
Both are deliberately dumb. Neither owns document state or knows what's inside it, so the same controls work in either.
It positions itself absolutely, so it needs a relative ancestor — normally
the element wrapping the viewer content. Without one it pins to whatever
positioned element it finds further up, which is usually the page.
Installation#
pnpm dlx shadcn@latest add pdfcn/floating-toolbar
Usage#
import {
PdfFloatingToolbar,
PdfFloatingToolbarSeparator,
} from "@/components/pdf-floating-toolbar";<div className="relative flex min-h-0 flex-1">
<PdfViewerContent className="flex-1" />
<PdfFloatingToolbar>
<PdfPointerToggle size="lg" />
<PdfPanToggle size="lg" />
<PdfFloatingToolbarSeparator />
<PdfAnnotationToolbar variant="default" size="lg" spacing={1} />
</PdfFloatingToolbar>
</div>Controls take size="lg" here rather than the size="sm" they default to. A docked bar is dense because it shares a row with everything else; a floating pill is a target you aim at, so it's built at the larger size.
<PdfAnnotationToolbar /> also drops its segmented borders (variant="default") and opens its spacing (spacing={1}), so its tools read as separate pills rather than one welded block.
Examples#
Default#
"use client";
import { createPluginRegistration } from "@embedpdf/core";Choosing an edge#
side picks the edge to pin to and align picks where along it to sit. A pill on the top or bottom edge runs horizontally; one on the left or right runs vertically, and its separators turn with it.
<PdfFloatingToolbar side="left" align="center">
{/* … */}
</PdfFloatingToolbar>Tooltips follow: they open on the far side of the pill from the edge it's anchored to, so they open into the page rather than into the edge of the viewer, where they'd have to flip or be clipped. A pill along the bottom labels upward; one along the left labels rightward.
To move it further from the edge, pass a class — side and align exist because they change which edges and transforms apply, and an offset is one class:
<PdfFloatingToolbar className="bottom-8" />One mutually exclusive set#
The interaction manager holds exactly one active mode per document, and every mode control arms one — pointer and pan from the interaction manager, drawing tools from the annotation plugin. So a row of them behaves as a single mutually exclusive set even though each reads its own plugin, and nothing here has to coordinate it:
<PdfFloatingToolbar>
<PdfPointerToggle size="lg" />
<PdfPanToggle size="lg" />
<PdfFloatingToolbarSeparator />
<PdfAnnotationToolbar
variant="default"
size="lg"
spacing={1}
tools={["square", "circle", "freeText", "highlight", "ink", "textComment"]}
/>
</PdfFloatingToolbar><PdfPointerToggle /> is the way back to none of them — it reads as pressed whenever nothing else is armed.
Adding your own tool#
The registry's controls already label themselves and drop straight in. <PdfFloatingToolbarItem /> is for the ones you add yourself: it matches their size and tooltip behaviour, so a custom tool doesn't read as a foreign object in the row.
<PdfFloatingToolbar>
<PdfPointerToggle size="lg" />
<PdfFloatingToolbarSeparator />
<PdfFloatingToolbarItem
label="Measure"
pressed={tool === "measure"}
onPressedChange={(pressed) => setTool(pressed ? "measure" : null)}
>
<RulerIcon />
</PdfFloatingToolbarItem>
</PdfFloatingToolbar>It's a toggle because everything in a tool pill is a mode — you arm it and it stays armed. For a one-shot action, put a plain <Button variant="ghost" size="icon-lg" /> in the toolbar instead; it's a dumb container and won't mind.
Alongside a docked toolbar#
The two aren't alternatives. A common split is document chrome docked at the top — paging, zoom, export — with the tools floating over the page:
<PdfViewer documents={[{ url: "/sample.pdf" }]} className="h-[720px]">
<PdfToolbar>
<PdfPageNavigation />
<PdfToolbarSeparator />
<PdfZoomControls />
</PdfToolbar>
<div className="relative flex min-h-0 flex-1">
<PdfViewerContent className="flex-1" />
<PdfFloatingToolbar>{/* … */}</PdfFloatingToolbar>
</div>
</PdfViewer><PdfAnnotatorApp /> is this layout in full.
Accessibility#
<PdfFloatingToolbar /> carries role="toolbar" and an aria-orientation matching the edge it's pinned to, so assistive technology reads a vertical rail as vertical.
Every item is icon-only, so each carries an aria-label — the same text its tooltip shows, which means the label is available whether or not the tooltip opens. <PdfFloatingToolbarItem /> takes one label prop and uses it for both, so the two can't drift apart.
Because the pill is absolutely positioned, it appears in tab order where it sits in the DOM, not where it sits on screen. Place it after <PdfViewerContent /> — as the examples do — so tabbing reaches the document before the tools that act on it.
API Reference#
PdfFloatingToolbar#
An absolutely positioned flex container carrying data-slot="pdf-floating-toolbar" and role="toolbar", plus data-side and data-orientation. Accepts every div prop.
| Prop | Type | Default | Description |
|---|---|---|---|
side | "top" | "right" | "bottom" | "left" | "bottom" | The edge of the positioned ancestor to pin to. |
align | "start" | "center" | "end" | "center" | Where along that edge to sit. |
Orientation is derived from side rather than set: left and right are vertical, top and bottom horizontal. There's no combination where a pill runs along the edge it's pinned to.
PdfFloatingToolbarItem#
A <Toggle /> carrying data-slot="pdf-floating-toolbar-item", wrapped in a tooltip. Accepts every <Toggle /> prop, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | The accessible name, and the text its tooltip shows. |
tooltipSide | "top" | "right" | "bottom" | "left" | Away from side | Overrides which side the tooltip opens on. |
Defaults to size="lg", the size a floating pill is built at.
PdfFloatingToolbarSeparator#
A <Separator /> carrying data-slot="pdf-floating-toolbar-separator", sized to the height of a control rather than the full pill. Accepts every <Separator /> prop.
It crosses the pill, so it takes the opposite orientation to the toolbar it's in — read from context, so a rail's separators turn with it and nothing has to be passed down by hand.