This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /blog/react-pdf-outline-table-of-contents.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Render a PDF table of contents with react-pdf outline

Table of contents

    This guide covers building a table of contents sidebar with react-pdf — using the Outline component to render a PDF’s bookmark tree, handle item clicks for page navigation, and style the outline with CSS.
    Render a PDF table of contents with react-pdf outline
    TL;DR
    • <Outline> must be a child of <Document> — it renders the PDF’s bookmark tree as nested <ul>/<li>/<a> elements.
    • Wire up onItemClick (on either <Document> or <Outline>) to receive { dest, pageIndex, pageNumber } and drive page navigation.
    • Not every PDF has an outline. Check onLoadSuccess for null, and hide the sidebar accordingly.
    • For full control over the markup, read raw outline data via useOutlineContext and render the tree yourself — see custom rendering modes and context hooks in react-pdf for the pattern.

    The Outline component renders the PDF’s built-in bookmark tree (table of contents). Clicking an item navigates to that section of the document.

    Basic usage

    Render Outline as a child of Document alongside your page view:

    import { Document, Page, Outline } from "react-pdf";
    function PDFWithOutline() {
    const [numPages, setNumPages] = useState(null);
    const [currentPage, setCurrentPage] = useState(1);
    return (
    <Document
    file={file}
    onLoadSuccess={({ numPages }) => setNumPages(numPages)}
    onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}
    >
    <div style={{ display: "flex" }}>
    <aside style={{ width: 250, overflowY: "auto" }}>
    <Outline />
    </aside>
    <main>
    <Page pageNumber={currentPage} />
    </main>
    </div>
    </Document>
    );
    }

    Outline props

    PropTypeDescription
    classNamestring/string[]CSS class(es) alongside .react-pdf__Outline.
    inputRefrefRef forwarded to the root <div>.
    onItemClickfunctionCalled when an outline item is clicked.
    onLoadSuccessfunctionCalled when outline is loaded.
    onLoadErrorfunctionCalled on error loading outline.

    Handling item clicks

    The onItemClick callback (on either Document or Outline) receives:

    <Outline
    onItemClick={({ dest, pageIndex, pageNumber }) => {
    // `dest`: the PDF destination object (for advanced navigation).
    // `pageIndex`: 0-indexed page number.
    // `pageNumber`: 1-indexed page number.
    console.log(`Navigate to page ${pageNumber}`);
    setCurrentPage(pageNumber);
    }}
    />

    onItemClick on <Document> catches clicks from any child <Outline>. This is useful for centralized navigation handling so each outline doesn’t need its own handler.

    Outline load callbacks

    onLoadSuccess and onLoadError report whether the outline loaded, and with what:

    <Outline
    onLoadSuccess={(outline) => {
    if (outline) {
    console.log("Outline loaded with items");
    } else {
    console.log("PDF has no outline");
    }
    }}
    onLoadError={(error) => {
    console.error("Failed to load outline:", error);
    }}
    />

    Styling the outline

    The outline renders as a nested list. Target it with CSS:

    .react-pdf__Outline {
    padding: 16px;
    }
    /* Top-level list. */
    .react-pdf__Outline ul {
    list-style: none;
    padding: 0;
    margin: 0;
    }
    /* Nested lists (subsections). */
    .react-pdf__Outline ul ul {
    padding-left: 16px;
    }
    /* Outline items. */
    .react-pdf__Outline li {
    margin: 2px 0;
    }
    /* Clickable links. */
    .react-pdf__Outline a {
    display: block;
    padding: 4px 8px;
    color: #333;
    text-decoration: none;
    border-radius: 4px;
    font-size: 14px;
    }
    .react-pdf__Outline a:hover {
    background-color: #f0f0f0;
    }

    Conditional rendering

    Not all PDFs have an outline. Handle this gracefully:

    function OutlineSidebar() {
    const [hasOutline, setHasOutline] = useState(true);
    if (!hasOutline) return null;
    return (
    <aside className="outline-sidebar">
    <h3>Table of Contents</h3>
    <Outline
    onLoadSuccess={(outline) => {
    if (!outline) setHasOutline(false);
    }}
    />
    </aside>
    );
    }

    Complete sidebar layout

    This puts it all together: an outline sidebar that hides itself when there’s no outline, plus page navigation:

    function PDFViewerWithSidebar() {
    const [numPages, setNumPages] = useState(null);
    const [currentPage, setCurrentPage] = useState(1);
    const [showOutline, setShowOutline] = useState(true);
    return (
    <Document
    file={file}
    onLoadSuccess={({ numPages }) => setNumPages(numPages)}
    onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}
    >
    <div className="pdf-layout">
    {showOutline && (
    <aside className="sidebar">
    <Outline
    onLoadSuccess={(outline) => {
    if (!outline) setShowOutline(false);
    }}
    />
    </aside>
    )}
    <main className="pdf-content">
    <Page pageNumber={currentPage} />
    <div className="page-nav">
    <button
    disabled={currentPage <= 1}
    onClick={() => setCurrentPage((p) => p - 1)}
    >
    Previous
    </button>
    <span>{currentPage} / {numPages}</span>
    <button
    disabled={currentPage >= numPages}
    onClick={() => setCurrentPage((p) => p + 1)}
    >
    Next
    </button>
    </div>
    </main>
    </div>
    </Document>
    );
    }

    Key points

    • Outline must be a child of Document.
    • Not all PDFs have an outline — check onLoadSuccess for null.
    • onItemClick gives you pageNumber (1-indexed) and pageIndex (0-indexed).
    • onItemClick on Document also catches outline clicks (useful for centralized navigation).
    • Style with CSS targeting .react-pdf__Outline classes.
    • The outline renders as nested <ul>/<li>/<a> elements.

    Rendering the outline yourself

    If you need full control over the markup — to use your own component library, add icons per level, or virtualize a deeply nested tree — read the raw outline data and render it yourself. react-pdf exposes useOutlineContext for children of <Outline>, and onLoadSuccess(outline) hands you the same tree structure. See custom rendering modes and context hooks in react-pdf for the full pattern.

    How Nutrient Web SDK handles this

    All the custom outline components, click handlers, and conditional rendering above reduce to a single line with Nutrient Web SDK:

    // Built-in bookmark sidebar via `sidebarMode` — one line.
    instance.setViewState((v) =>
    v.set("sidebarMode", NutrientViewer.SidebarMode.BOOKMARKS),
    );

    There’s no <Outline> component, no onItemClick handler, no conditional rendering for missing outlines, and no CSS styling. Nutrient’s built-in sidebar displays the bookmark tree with nested navigation, automatic scroll-to-page, and keyboard accessibility — all styled and ready to use.

    Learn more about Nutrient Web SDK | Migration guide | Contact Sales

    FAQ

    Why doesn’t my PDF show an outline?

    Not every PDF has one. Outlines are an authoring choice — Word, InDesign, and LaTeX (with hyperref) typically generate them; quick exports from browsers or scanners usually don’t. Check onLoadSuccess — if it receives null, the PDF has no bookmark tree and there’s nothing for <Outline> to render.

    What’s the difference between pageIndex and pageNumber in onItemClick?

    pageIndex is 0-indexed (matches the underlying PDF.js page array); pageNumber is 1-indexed (matches user-facing UI like “Page 5 of 20”). Use whichever fits the rest of your code, but be consistent — mixing them is a common off-by-one source.

    Should I put onItemClick on <Document> or <Outline>?

    Either works. Put it on <Document> when you want one handler for all outline activity in the tree. Put it on <Outline> when you have multiple outlines (rare) or want to attach behavior specific to that instance — for example, expanding/collapsing UI state.

    What if the user loads a new PDF — does the outline reset?

    react-pdf remounts the <Outline> when the parent <Document>’s file prop changes, so the new outline loads automatically. If you tracked hasOutline in component state (as in the conditional rendering example), reset it in a useEffect keyed on file so a missing-outline state from a previous PDF doesn’t carry over.

    Can I style the outline to match my app’s theme?

    Yes — the outline renders as nested <ul>, <li>, and <a> elements under .react-pdf__Outline. Override colors, spacing, and hover states with normal CSS. For richer control (custom icons, virtualization, or component-library wrappers), render the tree yourself using the outline data from onLoadSuccess or useOutlineContext.

    Does the outline scroll the page into view automatically?

    No — react-pdf only fires onItemClick. You’re responsible for updating the page state (setCurrentPage) and, if you scroll-render pages, scrolling the right page into view. Some setups also call dest against the PDF.js link service for precise position (zoom + offset); for most apps, pageNumber is enough.

    Austin Nguyen

    Austin Nguyen

    AI Engineer

    When Austin isn’t pulling all-nighters to build new features, he enjoys watching science videos on YouTube and cooking.

    Explore related topics

    Try for free Ready to get started?