React pdf kit custom layout
Skill react-pdf-kit/agent-skills/skills/react-pdf-kit-custom-layout
Skills to help developers using AI agents with React PDF Kit library
npx -y skills add react-pdf-kit/agent-skills --skill react-pdf-kit-custom-layoutAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Build a fully headless / custom layout for @react-pdf-kit/viewer (>=2.0.0 <3.0.0) using only documented hooks (useDocumentContext, useZoomContext, usePaginationContext, etc.), replacing RPLayout entirely.
SKILL.md
6.7 KB, as published. Nobody here has run it
react-pdf-kit-custom-layout
Use this skill when: the developer asks to replace the default
viewer layout entirely with their own. Examples: embedded inline
preview, a sidebar-only minimal reader, an enterprise UI with a
custom toolbar on the side instead of the top. For small toolbar
tweaks while keeping the default layout, use
react-pdf-kit-toolbar-customization.
The viewer is composable. RPProvider and RPPages are the minimum
required. Everything else (RPLayout, RPTheme, individual toolbar
tools) can be swapped or omitted. State and actions are exposed via
documented hooks.
Gotchas
RPProviderandRPPagesare required.RPProvidermounts the document and makes the contexts available.RPPagesrenders the virtualized page list. Removing either breaks the viewer.- Use only documented hooks. Reaching into internal contexts via
React.useContext(InternalContext)is unsupported and will break on minor releases. Documented hooks:useDocumentContext,useZoomContext,usePaginationContext,useSearchContext,useHighlightContext,useRotationContext,useViewModeContext,useDarkModeContext, plus feature hooks underutils/hooks/re-exported from the public entry. - Do not break virtualization. Wrapping
RPPagesin any ancestor that doesn't have a measurable height (noflex: 1/min-height: 0, no fixed height) makes the virtualizer mount with 0 rows. Always give it a real viewport. - Provider order matters.
RPConfigmust be outermost, thenRPProvider, thenRPTheme(optional but harmless) inside that. Anywhere insideRPProvideryou can call the hooks. RPThemeis still recommended even in custom layouts. It provides CSS custom properties that built-in components (page layers, text selection highlight) read. Skipping it works visually but loses dark-mode and theming.- Note on
RPLayoutvsRPDefaultLayout: this skill replaces the default layout component, so neither appears in the final composition. Older code that importsRPDefaultLayoutshould migrate toRPLayoutfirst (RPDefaultLayoutis deprecated in v2) before headless migration, so the two refactors don't get tangled.
Procedure
1. Compose the minimum chain
// src/HeadlessPdfViewer.tsx
import {
RPConfig,
RPProvider,
RPTheme,
RPPages,
} from '@react-pdf-kit/viewer'
import { CustomShell } from './CustomShell'
export function HeadlessPdfViewer({ src }: { src: string }) {
return (
<RPConfig>
<RPProvider src={src}>
<RPTheme>
<CustomShell />
</RPTheme>
</RPProvider>
</RPConfig>
)
}
CustomShell (next step) is where YOU place RPPages next to your
own toolbar, sidebar, or status bar.
2. Build the shell using documented hooks
// src/CustomShell.tsx
import {
RPPages,
useDocumentContext,
useZoomContext,
usePaginationContext,
useDarkModeContext,
} from '@react-pdf-kit/viewer'
export function CustomShell() {
const { numPages, isLoading } = useDocumentContext()
const { zoom, setZoom } = useZoomContext()
const { currentPage, goToPage } = usePaginationContext()
const { isDarkMode, toggleDarkMode } = useDarkModeContext()
return (
<div
style={{
display: 'grid',
gridTemplateColumns: '240px 1fr',
gridTemplateRows: '48px 1fr',
height: '100%',
}}
>
<header
style={{
gridColumn: '1 / -1',
display: 'flex',
alignItems: 'center',
gap: 12,
padding: '0 16px',
borderBottom: '1px solid var(--rp-border, #e5e7eb)',
}}
>
<span>{isLoading ? 'Loading...' : `${numPages} pages`}</span>
<button
type="button"
onClick={() => setZoom(zoom + 0.1)}
aria-label="Zoom in"
>
+
</button>
<button
type="button"
onClick={() => setZoom(zoom - 0.1)}
aria-label="Zoom out"
>
-
</button>
<span style={{ flex: 1 }} />
<button
type="button"
onClick={toggleDarkMode}
aria-pressed={isDarkMode}
>
{isDarkMode ? 'Light' : 'Dark'}
</button>
</header>
<aside
style={{
overflow: 'auto',
borderRight: '1px solid var(--rp-border, #e5e7eb)',
padding: 8,
}}
>
{Array.from({ length: numPages }, (_, i) => i + 1).map(p => (
<button
key={p}
type="button"
onClick={() => goToPage(p)}
aria-current={p === currentPage ? 'page' : undefined}
style={{ display: 'block', width: '100%', textAlign: 'left' }}
>
Page {p}
</button>
))}
</aside>
<main style={{ overflow: 'hidden', minHeight: 0 }}>
<RPPages />
</main>
</div>
)
}
The <main> ancestor of RPPages MUST have min-height: 0
(combined with the parent grid's 1fr row) so the virtualizer's
parent has a measurable height.
3. Memoize where it matters
If you compute derived state to pass into RPProvider (for example,
a file-loading callback or options object), memoize it. The provider
re-renders all children on identity changes, so a new object every
render disables virtualization gains.
const options = useMemo(() => ({ withCredentials: true }), [])
return <RPProvider src={src} options={options}>...</RPProvider>
4. (Optional) Add search, rotation, view modes
Same pattern: import the hook, call it, render UI bound to its state
and actions. useSearchContext, useHighlightContext,
useRotationContext, useViewModeContext are all documented.
Verify
pnpm install
pnpm build
pnpm dev
Open the page. Confirm:
- The PDF renders inside your custom shell.
- The page-jump sidebar updates
currentPage(verify thearia-currentindicator). - Zoom buttons resize pages without remounting the viewer.
- Dark-mode toggle flips the theme.
- Scrolling still virtualizes. Only mounted pages should be in the
DOM (DevTools, Elements panel:
RPPagesshould render a windowed subset, not all pages at once).
References
- Companion skills:
react-pdf-kit-setup: first-time setup.react-pdf-kit-toolbar-customization: for keeping the default layout but tweaking toolbar contents.