A ?-triggered cheatsheet listing every keyboard shortcut the viewer has bound.
"use client";
import { createPluginRegistration } from "@embedpdf/core";About#
The ? overlay, the way Linear and GitHub do it. It reads the live command registry, so it lists what this app can do rather than what a preset defines — a command whose plugin was never registered is left out.
Optional. Pair it with PDF Keyboard Shortcuts, which binds the keys; they work with or without this mounted.
Installation#
pnpm dlx shadcn@latest add @pdfviewcn/shortcuts-dialog
Usage#
import { PdfShortcutsDialog } from "@/components/pdf-shortcuts-dialog";<PdfViewer documents={[{ url: "/sample.pdf" }]} plugins={plugins}>
<PdfToolbar>
<div className="ml-auto">
<PdfShortcutsDialog />
</div>
</PdfToolbar>
<PdfViewerContent />
</PdfViewer>plugins has to carry createKeyboardShortcutsRegistration() — see Usage. Without it the sheet opens empty and says so.
The list is taken fresh on every open, so a search panel or layout that mounted after the dialog still shows up.
Examples#
Default#
"use client";
import { createPluginRegistration } from "@embedpdf/core";Without a trigger#
? opens it from anywhere in the viewer, so the button is optional:
<PdfShortcutsDialog trigger={null} />Custom trigger#
<PdfShortcutsDialog trigger={<Button variant="outline">Shortcuts</Button>} />Section order#
<PdfShortcutsDialog categoryOrder={["Tools", "Editing", "Navigation"]} />Unlisted categories trail after in registration order.
Accessibility#
The default trigger is a button labelled Keyboard shortcuts, so the label survives without the tooltip. Focus, Esc, and the return of focus on close come from Dialog.
Each category is a <section> with a heading, so the sheet reads as a list of groups rather than one run of rows. Keys are <Kbd> elements — announced as text, not images.
? is bound by the preset's global listener, which ignores keystrokes
in inputs, so typing ? into a search field won't open it.
API Reference#
PdfShortcutsDialog#
Renders the cheatsheet with data-slot="pdf-shortcuts-dialog" on the content, and each row as data-slot="pdf-shortcuts-dialog-row". Extends Dialog, so open, defaultOpen, and onOpenChange pass through.
| Prop | Type | Default | Description |
|---|---|---|---|
trigger | React.ReactElement | null | keyboard button | Element that opens the dialog. null renders none — ? still opens it. |
categoryOrder | string[] | Nav, Zoom, … | Category order, top to bottom. Unlisted categories trail after. |
Commands sharing a label fold into one row and pool their keys, which is why the four nudge directions read as ↑ ↓ ← → on one line.
A command that reports itself invisible is dropped; one that's merely disabled right now — Delete with nothing selected — keeps its row, so the sheet doesn't rewrite itself while it's being read. See What each app gets.