Styling
Styling Guide
Section titled “Styling Guide”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.
Shell vs headless
Section titled “Shell vs headless”There are now two deliberate customization paths:
@scaryterry/pdfium/reactand@scaryterry/pdfium/react/editorship the opinionated viewer/editor shell. This is the path themed by the--pdfium-*token system documented below.@scaryterry/pdfium/react/headlessships 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:
ViewerandEditorprove the shipped shell + token path.Headless ViewerandHeadless Editorprove the public headless composition path.- The showcase theme controls now separate
mode(light,dark,system) from brandedpresetpacks, 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.
Styling philosophy
Section titled “Styling philosophy”The library uses a five-layer approach, from most constrained to most flexible:
| Layer | What it controls | How it works |
|---|---|---|
| Structural | Layout essentials (flex, overflow, position, contain) | Inline style applied by the library — never overridden |
| Themeable | Visual properties (colours, shadows, borders) | CSS custom properties prefixed --pdfium-* |
| Override | Full consumer control on the root element | className and style props on every component |
| Sub-elements | Fine-grained class targeting on inner elements | classNames object prop (e.g. classNames.page) |
| Complex UI | Toolbar controls, search bar, error fallback | Render 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.
CSS custom properties
Section titled “CSS custom properties”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.
PDFDocumentView
Section titled “PDFDocumentView”| Property | Default | Description |
|---|---|---|
--pdfium-container-bg | #e8eaed | Background colour of the scroll container (the grey surround behind pages) |
--pdfium-loading-colour | inherit | Text colour of the default “Loading document…” placeholder |
PDFPageView
Section titled “PDFPageView”| Property | Default | Description |
|---|---|---|
--pdfium-page-bg | #fff | Background colour of a loaded page |
--pdfium-page-bg-loading | #f3f4f6 | Background colour shown while the page bitmap is loading |
--pdfium-page-shadow | 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08) | Box shadow around each page |
--pdfium-page-border | none | Border around each page |
Viewer and editor shell
Section titled “Viewer and editor shell”These are the core tokens behind PDFViewer, DefaultToolbar, PDFEditor, the shipped panel system, and the redaction confirmation dialog.
| Property | Default | Description |
|---|---|---|
--pdfium-toolbar-bg | #fff | Toolbar background |
--pdfium-toolbar-border | #e5e7eb | Toolbar border colour |
--pdfium-toolbar-colour | #374151 | Toolbar text/icon colour |
--pdfium-toolbar-radius | 4px | Shared chrome radius for toolbar, panel surfaces, and dialog shells |
--pdfium-activity-bar-width | 84px | Width of the labelled left activity rail |
--pdfium-activity-bar-bg | #f9fafb | Activity rail background |
--pdfium-sidebar-bg | #fff | Panel/sidebar background |
--pdfium-sidebar-header-bg | #f9fafb | Panel header background |
--pdfium-panel-section-bg | #f9fafb | Section background inside viewer/editor panels |
--pdfium-panel-section-border | #e5e7eb | Section border inside viewer/editor panels |
--pdfium-panel-btn-bg | #3b82f6 | Primary panel button background |
--pdfium-panel-btn-border | #2563eb | Primary panel button border |
--pdfium-panel-btn-secondary-bg | #f3f4f6 | Secondary panel button background |
--pdfium-panel-btn-secondary-border | #d1d5db | Secondary panel button border |
--pdfium-panel-danger-bg | #fef2f2 | Destructive button/surface background |
--pdfium-panel-danger-border | #fecaca | Destructive button/surface border |
--pdfium-dialog-backdrop | rgba(15, 23, 42, 0.36) | Modal backdrop used by shipped dialogs |
--pdfium-dialog-bg | #fff | Modal surface background |
--pdfium-dialog-border | #e5e7eb | Modal surface border |
--pdfium-dialog-shadow | 0 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.
ThumbnailStrip
Section titled “ThumbnailStrip”| Property | Default | Description |
|---|---|---|
--pdfium-thumb-active-colour | #3b82f6 | Outline colour of the currently active thumbnail |
--pdfium-thumb-shadow | 0 0 0 1px rgba(0,0,0,0.1) | Box shadow on each thumbnail canvas |
--pdfium-thumb-label-colour | #6b7280 | Text colour of the page number label below each thumbnail |
BookmarkPanel
Section titled “BookmarkPanel”| Property | Default | Description |
|---|---|---|
--pdfium-bookmark-active-colour | #3b82f6 | Text colour of the active bookmark (matching the current page) |
--pdfium-bookmark-indent | 16px | Per-level indentation for nested bookmark items |
TextOverlay
Section titled “TextOverlay”| Property | Default | Description |
|---|---|---|
--pdfium-selection-colour | rgba(20, 100, 255, 0.3) | Background colour of selected text (applied via ::selection) |
Note:
TextOverlayrespects the themed--pdfium-selection-colourby default. Passing theselectionColourprop overrides that token for the specific instance (see Props-based colour configuration below).
Props-based colour configuration
Section titled “Props-based colour configuration”Some overlay components accept colour props directly, because they paint onto <canvas> elements where CSS custom properties cannot reach.
TextOverlay
Section titled “TextOverlay”| Prop | Type | Default | Description |
|---|---|---|---|
selectionColour | string | 'rgba(20, 100, 255, 0.3)' | Background colour of text selection. Overrides --pdfium-selection-colour for that instance. |
SearchHighlightOverlay
Section titled “SearchHighlightOverlay”| Prop | Type | Default | Description |
|---|---|---|---|
currentMatchColour | string | 'rgba(255, 165, 0, 0.4)' | Fill colour for the active search match |
otherMatchColour | string | 'rgba(255, 255, 0, 0.35)' | Fill colour for all other search matches |
CharacterInspectorOverlay
Section titled “CharacterInspectorOverlay”| Prop | Type | Default | Description |
|---|---|---|---|
boxStroke | string | 'rgba(220, 38, 38, 0.9)' | Stroke colour of the character bounding box |
boxFill | string | 'rgba(220, 38, 38, 0.1)' | Fill colour of the character bounding box |
classNames reference
Section titled “classNames reference”Several components expose a classNames object prop for targeting inner elements without having to use render props or deep CSS selectors.
PDFDocumentViewClassNames
Section titled “PDFDocumentViewClassNames”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', }}/>ThumbnailStripClassNames
Section titled “ThumbnailStripClassNames”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.activeis used instead ofclassNames.thumbnail, not in addition to it. If you need shared styles, compose them in both class names or use a common utility class.
BookmarkPanelClassNames
Section titled “BookmarkPanelClassNames”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.activeis used instead ofclassNames.item, not in addition to it. If you need shared styles, compose them in both class names.
PDFViewerClassNames
Section titled “PDFViewerClassNames”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', }}/>Z-index stacking order
Section titled “Z-index stacking order”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-index | Layer | Purpose |
|---|---|---|
| (base) | PDFCanvas | Rendered page bitmap |
| 10 | TextOverlay | Transparent selectable text spans |
| 15 | LinkOverlay | Clickable link hit regions |
| 20 | SearchHighlightOverlay | Search match rectangles |
| 30 | AnnotationOverlay | Annotation bounding boxes |
| 40 | CharacterInspectorOverlay | Character inspection hit surface |
| 50 | DragDropZone overlay | File 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.
Theming recipes
Section titled “Theming recipes”Dark mode
Section titled “Dark mode”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)" /></>Tailwind CSS integration
Section titled “Tailwind CSS integration”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', }}/>Minimal / clean look
Section titled “Minimal / clean look”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" />Branded colours
Section titled “Branded colours”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)"/>What is NOT styled
Section titled “What is NOT styled”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:
| Element | What you get | What you need to provide |
|---|---|---|
Toolbar buttons (DefaultToolbar, PDFToolbar) | Native <button> and <select> elements with no visual styling | All button/input styling (colours, padding, borders, hover states) |
| SearchPanel controls | Native <input type="search"> and <button> with flex layout only | Input styling, button styling, spacing, colours |
Error boundary fallback (PDFiumErrorBoundary) | Minimal inline styles with light red background | Override via fallbackRender or fallback prop for full control |
| DragDropZone overlay | Basic dashed border with light blue tint | Override via renderDragOverlay prop for custom drop indicator |
Link hover styles (LinkOverlay) | Invisible hit regions with cursor: pointer | Hover/focus styling if you want visible link indicators |
Annotation colours (AnnotationOverlay) | Hard-coded blue (normal) and red (selected) strokes on canvas | Pass your own canvas drawing via renderPageOverlay for custom annotation visuals |
Minimap rectangle (PageNavigatorMinimap) | Hard-coded blue fill/stroke SVG rectangle | Use className and style on the component; the viewport rectangle is not independently themeable |
| Overall layout | No wrapper div, no height/width constraints on the root | You must provide a sized container (the viewer fills its parent) |
| Scrollbar | Browser-default scrollbar on the scroll container | Style 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>