Skip to content

Styling

Scope: React viewer toolkit (@scaryterry/pdfium/react).

@scaryterry/pdfium/react is designed to be branded into a product UI without forking the component tree. The core components ship the structural styles needed for correct behaviour, then expose CSS variables, slot class names, and render props so you can turn the viewer or editor shell into a first-class product surface.

Start from the flagship viewer/editor composition, theme it with tokens and class names, and reach for render props only when you need to replace chrome entirely.

There are now two deliberate customization paths:

  • @scaryterry/pdfium/react and @scaryterry/pdfium/react/editor ship the opinionated viewer/editor shell. This is the path themed by the --pdfium-* token system documented below.
  • @scaryterry/pdfium/react/headless ships the provider, hooks, and page/layer primitives without the shipped shell chrome. Use it when you want to own the toolbar, panels, action bars, and overall product styling yourself.

That distinction matters: the token system is a shell-branding surface, not the primary extensibility model for fully custom products.

The Vite showcase demonstrates both stories explicitly:

  • Viewer and Editor prove the shipped shell + token path.
  • Headless Viewer and Headless Editor prove the public headless composition path.
  • The showcase theme controls now separate mode (light, dark, system) from branded preset packs, so you can see the same runtime under multiple shell identities.
  • The showcase onboarding copy and expanded header controls keep the contract explicit: shell routes are themed through --pdfium-*, headless routes theme only the demo-owned chrome around the runtime.

The library uses a five-layer approach, from most constrained to most flexible:

LayerWhat it controlsHow it works
StructuralLayout essentials (flex, overflow, position, contain)Inline style applied by the library — never overridden
ThemeableVisual properties (colours, shadows, borders)CSS custom properties prefixed --pdfium-*
OverrideFull consumer control on the root elementclassName and style props on every component
Sub-elementsFine-grained class targeting on inner elementsclassNames object prop (e.g. classNames.page)
Complex UIToolbar controls, search bar, error fallbackRender props — zero visual opinion from the library

Structural styles are not themeable. The library sets position: relative, overflow: hidden, display: flex, etc. directly on elements because they are required for correct behaviour (virtualisation, stacking, scrolling). These are applied as inline styles and always win in specificity. If you need to override structural layout, use the style prop on the relevant component.

Visual styles are always themeable. Background colours, shadows, borders, and accent colours all read from CSS custom properties with sensible defaults. You can set these properties on any ancestor element and they will cascade down.


Every --pdfium-* custom property can be set on a parent element (or :root) to theme all instances at once, or on the component’s own style prop for per-instance control.

PDFIUM_THEME_VARIABLES exported from @scaryterry/pdfium/react is the complete shipped registry. The tables below document the most important shell tokens and must stay aligned with that runtime catalogue.

PropertyDefaultDescription
--pdfium-container-bg#e8eaedBackground colour of the scroll container (the grey surround behind pages)
--pdfium-loading-colourinheritText colour of the default “Loading document…” placeholder
PropertyDefaultDescription
--pdfium-page-bg#fffBackground colour of a loaded page
--pdfium-page-bg-loading#f3f4f6Background colour shown while the page bitmap is loading
--pdfium-page-shadow0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08)Box shadow around each page
--pdfium-page-bordernoneBorder around each page

These are the core tokens behind PDFViewer, DefaultToolbar, PDFEditor, the shipped panel system, and the redaction confirmation dialog.

PropertyDefaultDescription
--pdfium-toolbar-bg#fffToolbar background
--pdfium-toolbar-border#e5e7ebToolbar border colour
--pdfium-toolbar-colour#374151Toolbar text/icon colour
--pdfium-toolbar-radius4pxShared chrome radius for toolbar, panel surfaces, and dialog shells
--pdfium-activity-bar-width84pxWidth of the labelled left activity rail
--pdfium-activity-bar-bg#f9fafbActivity rail background
--pdfium-sidebar-bg#fffPanel/sidebar background
--pdfium-sidebar-header-bg#f9fafbPanel header background
--pdfium-panel-section-bg#f9fafbSection background inside viewer/editor panels
--pdfium-panel-section-border#e5e7ebSection border inside viewer/editor panels
--pdfium-panel-btn-bg#3b82f6Primary panel button background
--pdfium-panel-btn-border#2563ebPrimary panel button border
--pdfium-panel-btn-secondary-bg#f3f4f6Secondary panel button background
--pdfium-panel-btn-secondary-border#d1d5dbSecondary panel button border
--pdfium-panel-danger-bg#fef2f2Destructive button/surface background
--pdfium-panel-danger-border#fecacaDestructive button/surface border
--pdfium-dialog-backdroprgba(15, 23, 42, 0.36)Modal backdrop used by shipped dialogs
--pdfium-dialog-bg#fffModal surface background
--pdfium-dialog-border#e5e7ebModal surface border
--pdfium-dialog-shadow0 24px 48px rgba(15, 23, 42, 0.18)Modal surface shadow

PDFEditor deliberately reuses those same shell tokens. The left annotations editing workspace, redaction review block, and confirmation dialog should theme alongside the viewer chrome, not as a separate product.

PropertyDefaultDescription
--pdfium-thumb-active-colour#3b82f6Outline colour of the currently active thumbnail
--pdfium-thumb-shadow0 0 0 1px rgba(0,0,0,0.1)Box shadow on each thumbnail canvas
--pdfium-thumb-label-colour#6b7280Text colour of the page number label below each thumbnail
PropertyDefaultDescription
--pdfium-bookmark-active-colour#3b82f6Text colour of the active bookmark (matching the current page)
--pdfium-bookmark-indent16pxPer-level indentation for nested bookmark items
PropertyDefaultDescription
--pdfium-selection-colourrgba(20, 100, 255, 0.3)Background colour of selected text (applied via ::selection)

Note: TextOverlay respects the themed --pdfium-selection-colour by default. Passing the selectionColour prop overrides that token for the specific instance (see Props-based colour configuration below).


Some overlay components accept colour props directly, because they paint onto <canvas> elements where CSS custom properties cannot reach.

PropTypeDefaultDescription
selectionColourstring'rgba(20, 100, 255, 0.3)'Background colour of text selection. Overrides --pdfium-selection-colour for that instance.
PropTypeDefaultDescription
currentMatchColourstring'rgba(255, 165, 0, 0.4)'Fill colour for the active search match
otherMatchColourstring'rgba(255, 255, 0, 0.35)'Fill colour for all other search matches
PropTypeDefaultDescription
boxStrokestring'rgba(220, 38, 38, 0.9)'Stroke colour of the character bounding box
boxFillstring'rgba(220, 38, 38, 0.1)'Fill colour of the character bounding box

Several components expose a classNames object prop for targeting inner elements without having to use render props or deep CSS selectors.

interface PDFDocumentViewClassNames {
container?: string; // Outer scroll container
page?: string; // Each PDFPageView wrapper
loadingPlaceholder?: string; // "Loading document..." fallback
}

Usage:

<PDFDocumentView
scale={1}
classNames={{
container: 'my-scroll-area',
page: 'my-page',
loadingPlaceholder: 'my-loading',
}}
/>
interface ThumbnailStripClassNames {
container?: string; // Outer scrollable sidebar
thumbnail?: string; // Inactive thumbnail button
active?: string; // Active (selected) thumbnail button -- replaces `thumbnail` when active
label?: string; // Page number label below each thumbnail
}

Note: When a thumbnail is active, classNames.active is used instead of classNames.thumbnail, not in addition to it. If you need shared styles, compose them in both class names or use a common utility class.

interface BookmarkPanelClassNames {
container?: string; // Outer tree container (role="tree")
item?: string; // Inactive bookmark item
active?: string; // Active bookmark item (replaces `item` when active)
toggle?: string; // Expand/collapse arrow
title?: string; // Bookmark title text
filter?: string; // Filter input field
}

Note: When a bookmark is active, classNames.active is used instead of classNames.item, not in addition to it. If you need shared styles, compose them in both class names.

interface PDFViewerClassNames {
root?: string; // Outermost viewer wrapper (flex column)
toolbar?: string; // DefaultToolbar container
search?: string; // SearchPanel container
content?: string; // Content area (flex row containing panel chrome + pages)
activityBar?: string; // Activity bar wrapper (panel mode)
panel?: string; // Active panel container (panel mode)
pages?: string; // PDFViewer.Pages wrapper
}

Usage with the default layout:

<PDFViewer
classNames={{
root: 'viewer-root',
toolbar: 'viewer-toolbar',
search: 'viewer-search',
content: 'viewer-content',
activityBar: 'viewer-activity-bar',
panel: 'viewer-panel',
pages: 'viewer-pages',
}}
/>

Inside each PDFPageView, overlay layers are stacked using the following z-index values. The base canvas has no explicit z-index (it sits at the default stacking level).

z-indexLayerPurpose
(base)PDFCanvasRendered page bitmap
10TextOverlayTransparent selectable text spans
15LinkOverlayClickable link hit regions
20SearchHighlightOverlaySearch match rectangles
30AnnotationOverlayAnnotation bounding boxes
40CharacterInspectorOverlayCharacter inspection hit surface
50DragDropZone overlayFile drop indicator

All overlays use position: absolute; inset: 0 to fill their parent page container. The DragDropZone overlay is a top-level overlay that covers the entire viewer, not just a single page.

If you add custom overlays via renderPageOverlay, choose a z-index that fits within this stack. For example, a custom bounding-box highlight would sit comfortably between 20 and 30.


Set CSS custom properties on a parent with a dark-mode selector:

[data-theme='dark'] {
--pdfium-container-bg: #1a1a2e;
--pdfium-page-bg: #16213e;
--pdfium-page-bg-loading: #1a1a2e;
--pdfium-page-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px -1px rgba(0, 0, 0, 0.3);
--pdfium-page-border: 1px solid rgba(255, 255, 255, 0.08);
--pdfium-loading-colour: #9ca3af;
--pdfium-thumb-active-colour: #60a5fa;
--pdfium-thumb-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(0, 0, 0, 0.3);
--pdfium-thumb-label-colour: #9ca3af;
}

For the canvas-drawn overlays, pass colour props:

<>
<TextOverlay selectionColour="rgba(96, 165, 250, 0.35)" />
<SearchHighlightOverlay
currentMatchColour="rgba(251, 191, 36, 0.5)"
otherMatchColour="rgba(253, 224, 71, 0.3)"
/>
</>

Use Tailwind utility classes via className and classNames props. CSS custom properties can be set with Tailwind’s arbitrary property syntax:

<PDFDocumentView
scale={scale}
className="rounded-lg border border-gray-200 dark:border-gray-700"
style={{
'--pdfium-container-bg': 'var(--color-gray-100)',
'--pdfium-page-shadow': 'var(--shadow-md)',
} as React.CSSProperties}
classNames={{
page: 'transition-shadow hover:shadow-lg',
loadingPlaceholder: 'text-gray-400 animate-pulse',
}}
/>

For the PDFViewer compound component:

<PDFViewer
panels={['thumbnails', 'bookmarks']}
classNames={{
root: 'h-full flex flex-col bg-white dark:bg-gray-900',
toolbar: 'flex items-center gap-2 px-4 py-2 border-b border-gray-200 dark:border-gray-700',
content: 'flex flex-1 min-h-0',
activityBar: 'border-r border-gray-200 dark:border-gray-700',
panel: 'w-56 border-r border-gray-200 dark:border-gray-700 overflow-y-auto',
pages: 'flex-1 min-h-0',
}}
/>

Remove shadows and borders for a flat appearance:

.pdf-minimal {
--pdfium-container-bg: #ffffff;
--pdfium-page-shadow: none;
--pdfium-page-border: none;
--pdfium-thumb-shadow: none;
}
<PDFDocumentView scale={scale} className="pdf-minimal" />

Apply your organisation’s colour palette:

.my-brand {
--pdfium-container-bg: #f0f4f8;
--pdfium-thumb-active-colour: #e63946;
--pdfium-thumb-label-colour: #457b9d;
--pdfium-page-shadow: 0 2px 8px rgba(69, 123, 157, 0.15);
}

Override search highlight colours to match:

<SearchHighlightOverlay
currentMatchColour="rgba(230, 57, 70, 0.4)"
otherMatchColour="rgba(230, 57, 70, 0.15)"
/>

The following elements ship with either no visual styles or minimal functional-only styles. The library intentionally leaves these unstyled so you have full control:

ElementWhat you getWhat you need to provide
Toolbar buttons (DefaultToolbar, PDFToolbar)Native <button> and <select> elements with no visual stylingAll button/input styling (colours, padding, borders, hover states)
SearchPanel controlsNative <input type="search"> and <button> with flex layout onlyInput styling, button styling, spacing, colours
Error boundary fallback (PDFiumErrorBoundary)Minimal inline styles with light red backgroundOverride via fallbackRender or fallback prop for full control
DragDropZone overlayBasic dashed border with light blue tintOverride via renderDragOverlay prop for custom drop indicator
Link hover styles (LinkOverlay)Invisible hit regions with cursor: pointerHover/focus styling if you want visible link indicators
Annotation colours (AnnotationOverlay)Hard-coded blue (normal) and red (selected) strokes on canvasPass your own canvas drawing via renderPageOverlay for custom annotation visuals
Minimap rectangle (PageNavigatorMinimap)Hard-coded blue fill/stroke SVG rectangleUse className and style on the component; the viewport rectangle is not independently themeable
Overall layoutNo wrapper div, no height/width constraints on the rootYou must provide a sized container (the viewer fills its parent)
ScrollbarBrowser-default scrollbar on the scroll containerStyle via CSS ::webkit-scrollbar or scrollbar-* properties on the container class

DefaultToolbar and SearchPanel are the shipped baseline viewer surfaces. Theme or extend them first, then drop to PDFToolbar or the PDFViewer render-prop layout only when your product needs materially different chrome:

<PDFViewer>
{(state) => (
<>
<MyCustomToolbar viewer={state.viewer} />
<PDFViewer.Pages />
</>
)}
</PDFViewer>
  • PDFViewer - classNames slot map and layout composition points.
  • Toolbar - Headless toolbar surface for fully custom visual systems.
  • Examples - Practical patterns combining theming with custom layout.