Skip to content
Adjust Space Between Pages

You want to adjust space between pages in the React PDF Viewer instead of displaying default spaces between pages.

  • Pages look cramped and you want a visible gap between them for reading or screenshots.
  • You want viewers to change that gap while the PDF is open (for example a slider or number field).
  • Your layout should work when the viewer only keeps some pages in the DOM at once, not the full document at every scroll position.
  • You want no extra gap on a single-page file, or when the chosen gap is zero.

You can control the gap between pages and update it while the PDF stays open, while still using React PDF Kit’s page list and scroll behavior.

First, the document need to be loaded and ready. useDocumentContext tells you when the document is ready and how many pages it has.

NameObjective
pdfThe loaded PDF document. This example reads pdf.numPages to size the gaps.
loadingWhether the document is still loading. Wait until this is false before changing the page list.

Once the document is ready, pages can be accessed via the data-rp attribute from React PDF Kit’s viewer element markup.

NameObjective
pagesThe wrapper element that contains the PDF page list. This is the first element we query before applying spacing.

After the pages are accessed, you will need to use the following browser APIs to manage the gap between pages when the DOM changes. The React PDF Viewer component only mounts some page nodes at a time.

NameObjective
MutationObserverRun spacing again when pages are added or removed from the list.
requestAnimationFramePoll each frame until [data-rp="pages"] exists after load (up to about 120 frames).
cancelAnimationFrameStop polling when spacing is set up or the component unmounts.
createElementCreate the <style> element when gap CSS is applied for the first time.
getElementByIdFind the style tag to update its CSS or remove it when spacing is cleared.
appendChildAttach the style tag to head so the gap rules take effect.
addEventListenerUpdate spacing when scroll shows a different set of page rows.
  1. Spacing utilities

    This module provides the default gap value and the functions that locate the pages list and enable spacing.

    src/pdfPageSpacing.js
    export const PAGE_GAP_PX = 24;
    const STYLE_TAG_ID = "pdf-page-gap-styles";
    const SPACING_CLASS_NAME = "pdf-pages-spaced";
    const PAGE_SELECTOR = '[data-rp="pages"]';
    function isValidSpacingInput(totalPages, gap) {
    return totalPages > 1 && gap > 0;
    }
    // Build CSS that adds visible space between PDF pages in the virtual list.
    //
    // Margins on page nodes do not work reliably here. Instead:
    // 1. box shadow below each page shows the gap (viewer background color).
    // 2. translateY on page 2 and later moves each page down by one more gap.
    function buildPageGapStyles(totalPages, gap) {
    if (!isValidSpacingInput(totalPages, gap)) return "";
    const rules = [
    `.pdf-pages-spaced [id^="page-"] {
    box-shadow: 0 ${gap}px 0 0 var(--rp-pages-background-color, #d8d8d8) !important;
    }`,
    ];
    // Page 1 stays at y=0; each later page shifts by one more gap.
    for (let page = 2; page <= totalPages; page++) {
    const offset = (page - 1) * gap;
    rules.push(
    `.pdf-pages-spaced [data-rp="page-${page}"] { transform: translateY(${offset}px) !important; }`,
    );
    }
    return rules.join("\n");
    }
    function getOrCreateStyleTag() {
    let styleEl = document.getElementById(STYLE_TAG_ID);
    if (!styleEl) {
    styleEl = document.createElement("style");
    styleEl.id = STYLE_TAG_ID;
    document.head.appendChild(styleEl);
    }
    return styleEl;
    }
    function injectPageGapStyles(totalPages, gap) {
    const styleEl = getOrCreateStyleTag();
    styleEl.textContent = buildPageGapStyles(totalPages, gap);
    }
    function removePageGapStyles() {
    document.getElementById(STYLE_TAG_ID)?.remove();
    }
    function findGridInner(pagesContainer) {
    let tallestElement = null;
    let tallestHeight = 0;
    for (const el of Array.from(pagesContainer.querySelectorAll("div"))) {
    if (el.style.position !== "relative" || !el.style.height) continue;
    const height = Number.parseFloat(el.style.height);
    if (height > tallestHeight) {
    tallestHeight = height;
    tallestElement = el;
    }
    }
    return tallestElement;
    }
    function adjustInnerScrollHeight(pagesContainer, gap, totalPages) {
    if (!isValidSpacingInput(totalPages, gap)) return;
    const inner = findGridInner(pagesContainer);
    if (!inner) return;
    if (inner.dataset.originalHeight === undefined && inner.style.height) {
    inner.dataset.originalHeight = inner.style.height;
    }
    const originalHeight = Number.parseFloat(inner.dataset.originalHeight ?? "");
    if (Number.isNaN(originalHeight)) return;
    const targetHeight = originalHeight + (totalPages - 1) * gap;
    if (inner.style.height !== `${targetHeight}px`) {
    inner.style.height = `${targetHeight}px`;
    }
    }
    function removeSpacingFromContainer(pagesContainer) {
    pagesContainer.classList.remove(SPACING_CLASS_NAME);
    pagesContainer.style.removeProperty("--pdf-page-gap");
    removePageGapStyles();
    const inner = findGridInner(pagesContainer);
    if (inner?.dataset.originalHeight) {
    inner.style.height = inner.dataset.originalHeight;
    }
    }
    function applySpacingToContainer(pagesContainer, gap, totalPages) {
    if (gap <= 0 || totalPages <= 1) {
    removeSpacingFromContainer(pagesContainer);
    return;
    }
    pagesContainer.style.setProperty("--pdf-page-gap", `${gap}px`);
    pagesContainer.classList.add(SPACING_CLASS_NAME);
    injectPageGapStyles(totalPages, gap);
    adjustInnerScrollHeight(pagesContainer, gap, totalPages);
    }
    function findScrollTarget(pagesContainer) {
    return (
    pagesContainer.querySelector('[style*="overflow"]') ??
    pagesContainer.parentElement
    );
    }
    export function findPagesContainer(root) {
    return root.querySelector(PAGE_SELECTOR);
    }
    export function enablePageSpacing(pagesContainer, gap, getTotalPages) {
    const updateSpacing = () => {
    const totalPages = getTotalPages();
    if (totalPages <= 0) return;
    applySpacingToContainer(pagesContainer, gap, totalPages);
    };
    updateSpacing();
    const domObserver = new MutationObserver(updateSpacing);
    domObserver.observe(pagesContainer, { childList: true, subtree: true });
    const scrollTarget = findScrollTarget(pagesContainer);
    scrollTarget?.addEventListener("scroll", updateSpacing, { passive: true });
    return () => {
    domObserver.disconnect();
    scrollTarget?.removeEventListener("scroll", updateSpacing);
    removeSpacingFromContainer(pagesContainer);
    };
    }
  2. Sync spacing with the loaded PDF

    This component waits for the document to load, then calls enablePageSpacing.

    src/PageSpacing.jsx
    import { useDocumentContext } from "@react-pdf-kit/viewer";
    import { useEffect, useRef } from "react";
    import { enablePageSpacing, findPagesContainer } from "./pdfPageSpacing";
    export function PageSpacing({ viewerRef, gap }) {
    const { pdf, loading } = useDocumentContext();
    const cleanupSpacingRef = useRef(null);
    const clearSpacing = () => {
    cleanupSpacingRef.current?.();
    cleanupSpacingRef.current = null;
    };
    useEffect(() => {
    if (loading || !pdf) return;
    const totalPages = pdf.numPages;
    const trySetupSpacing = () => {
    const root = viewerRef.current;
    if (!root) return false;
    const pagesContainer = findPagesContainer(root);
    if (!pagesContainer) return false;
    clearSpacing();
    cleanupSpacingRef.current = enablePageSpacing(
    pagesContainer,
    gap,
    () => totalPages,
    );
    return true;
    };
    if (trySetupSpacing()) {
    return () => {
    clearSpacing();
    };
    }
    let attempts = 0;
    let frameId = 0;
    const retry = () => {
    if (trySetupSpacing() || attempts >= 120) return;
    attempts += 1;
    frameId = requestAnimationFrame(retry);
    };
    frameId = requestAnimationFrame(retry);
    return () => {
    cancelAnimationFrame(frameId);
    clearSpacing();
    };
    }, [pdf, loading, gap, viewerRef]);
    return null;
    }
  3. Gap input

    This component lets users pick a gap from 0 to 200 px.

    src/PageGapControl.jsx
    const MIN_GAP = 0;
    const MAX_GAP = 200;
    function parseGapInput(value) {
    const parsed = Number.parseInt(value, 10);
    if (Number.isNaN(parsed)) return MIN_GAP;
    return Math.min(MAX_GAP, Math.max(MIN_GAP, parsed));
    }
    export function PageGapControl({ gap, onGapChange }) {
    return (
    <label
    style={{
    display: "flex",
    alignItems: "center",
    gap: "8px",
    padding: "8px 12px",
    fontSize: "14px",
    borderBottom: "1px solid #e5e4e7",
    background: "#fff",
    }}
    >
    Page spacing (px)
    <input
    type="number"
    min={MIN_GAP}
    max={MAX_GAP}
    step={1}
    value={gap}
    onChange={(event) => onGapChange(parseGapInput(event.target.value))}
    style={{
    width: "72px",
    padding: "4px 8px",
    fontSize: "14px",
    }}
    />
    </label>
    );
    }
  4. Wire the viewer

    This step connects the gap state to the PDF viewer.

    src/App.jsx
    import { useRef, useState } from "react";
    import {
    RPConfig,
    RPLayout,
    RPPages,
    RPProvider,
    } from "@react-pdf-kit/viewer";
    import { PageGapControl } from "./PageGapControl";
    import { PageSpacing } from "./PageSpacing";
    import { PAGE_GAP_PX } from "./pdfPageSpacing";
    function App() {
    const viewerRef = useRef(null);
    const [pageGap, setPageGap] = useState(PAGE_GAP_PX);
    return (
    <div
    ref={viewerRef}
    style={{
    width: "100%",
    height: "700px",
    display: "flex",
    flexDirection: "column",
    }}
    >
    <h1 style={{ textAlign: "center" }}>Adjust Space Between Pages</h1>
    <PageGapControl gap={pageGap} onGapChange={setPageGap} />
    <div style={{ flex: 1, minHeight: 0 }}>
    <RPConfig>
    <RPProvider src="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf">
    <PageSpacing viewerRef={viewerRef} gap={pageGap} />
    <RPLayout toolbar style={{ width: "100%", height: "100%" }}>
    <RPPages />
    </RPLayout>
    </RPProvider>
    </RPConfig>
    </div>
    </div>
    );
    }
    export default App;
  • Render PageSpacing inside RPProvider so useDocumentContext can access the open file.
  • This approach depends on React PDF Kit data-rp DOM attributes.