Skip to content
Viewer Element Usage

React PDF provides data-rp attributes to access specific elements so that you can customize the viewer easily.

Attributedescription
data-rp="moreOptionsDropdown"The dropdown container of the More Options menu
data-rp="page-{pageNumber}"Page element
Remark: ReplacepageNumber with a specific value (e.g., data-rp="page-21")
data-rp="page-{pageNumber}-canvas"Page canvas element
Remark: ReplacepageNumber with a specific value (e.g., data-rp="page-21-canvas")
data-rp="page-{pageNumber}-textLayer"Page text layer element
Remark: ReplacepageNumber with a specific value (e.g., data-rp="page-21-textLayer")
data-rp="page-{pageNumber}-annotationLayer"Annotation layer element
Remark: ReplacepageNumber with a specific value (e.g., data-rp="page-21-annotationLayer")
data-rp="pages"Page container element
data-rp="sidebar"Sidebar container
data-rp="thumbnailSidebar"Thumbnail sidebar container
data-rp="topBar"The top bar container which consists of 3 sections (i.e. left, center and right)
data-rp="topBarCenter"The middle section of the top bar container which consists of the zoom feature
data-rp="topBarLeft"The left section of the top bar container which consists of search and page navigation
data-rp="topBarRight"The right section of the top bar container which consists of the rest of the features, including those in the More Options

Remark: If you would like to access other elements, please submit a request on GitHub.

Select Any Element (Except moreOptionsDropdown)

Section titled “Select Any Element (Except moreOptionsDropdown)”

Most elements can be accessed using DOM manipulation.

Section titled “Example: Access the page-{pageNumber}-annotationLayer Element to Detect a Link in a PDF Page”
  1. Create a state to store the dropdown position and the link href

    const [dropdown, setDropdown] = useState(null)
  2. Detect when a user clicks on a hyperlink inside the PDF Viewer

    const setupHyperlinkDetection = useCallback(() => {
    const root = containerRef.current
    if (!root) return undefined
    const onRootClick = (e) => {
    const target = e.target
    if (!target) return
    // Check if the clicked element is a hyperlink inside PDF layers
    const anchor = target.closest("a[href]")
    if (!anchor || !root.contains(anchor)) return
    const layer = anchor.closest('[data-rp*="textLayer"], [data-rp*="annotationLayer"]')
    if (!layer) return
    // Exclusion: skip when href is empty
    const hrefAttr = anchor.getAttribute("href") || "";
    if (hrefAttr === "") {
    return;
    }
    // Prevent default link behavior and show custom dropdown
    e.preventDefault()
    e.stopPropagation()
    const rect = anchor.getBoundingClientRect()
    setDropdown({
    href: anchor.href,
    x: rect.left + window.scrollX,
    y: rect.bottom + window.scrollY + 5,
    })
    }
    // Capture clicks inside the viewer
    root.addEventListener("click", onRootClick, true)
    return () => {
    root.removeEventListener("click", onRootClick, true)
    }
    }, [])
  3. Create a Dropdown UI to show actions when the user clicks a hyperlink

    const HyperlinkDropdown = ({ dropdown, onClose }) => {
    return (
    <div
    style={{
    position: "fixed",
    left: dropdown.x,
    top: dropdown.y,
    color: "#000",
    background: "#fff",
    border: "1px solid #ccc",
    borderRadius: "8px",
    padding: "8px",
    display: "flex",
    flexDirection: "column",
    gap: "8px",
    }}>
    <button
    aria-label="Close"
    onClick={onClose}
    style={{
    position: "absolute",
    top: 4,
    right: 4,
    border: "none",
    background: "transparent",
    cursor: "pointer",
    color: "#666",
    fontSize: "14px",
    lineHeight: 1,
    }}
    >
    </button>
    <button onClick={() => {
    window.open(dropdown.href, "_blank")
    onClose()
    }}>Open in new tab</button>
    <button onClick={async () => {
    await navigator.clipboard.writeText(dropdown.href)
    onClose()
    }}>Copy link</button>
    </div>
    )
    }
  4. Use useEffect to attach the event detection when the component is mounted

    useEffect(() => {
    const cleanup = setupHyperlinkDetection()
    return typeof cleanup === "function" ? cleanup : undefined
    }, [setupHyperlinkDetection])

Putting everything together

import React, { useCallback, useEffect, useRef, useState } from "react"
import {
RPProvider,
RPDefaultLayout,
RPPages,
} from "@pdf-viewer/react"
const HyperlinkDropdown = ({ dropdown, onClose }) => {
return (
<div
style={{
position: "fixed",
left: dropdown.x,
top: dropdown.y,
color: "#000",
background: "#fff",
border: "1px solid #ccc",
borderRadius: "8px",
padding: "8px",
display: "flex",
flexDirection: "column",
gap: "8px",
}}>
<button
aria-label="Close"
onClick={onClose}
style={{
position: "absolute",
top: 4,
right: 4,
border: "none",
background: "transparent",
cursor: "pointer",
color: "#666",
fontSize: "14px",
lineHeight: 1,
}}
>
</button>
<button onClick={() => {
window.open(dropdown.href, "_blank")
onClose()
}}>Open in new tab</button>
<button onClick={async () => {
await navigator.clipboard.writeText(dropdown.href)
onClose()
}}>Copy link</button>
</div>
)
}
const AppPdfViewer = () => {
// Reference to the viewer container
const containerRef = useRef(null)
// State for the hyperlink
const [dropdown, setDropdown] = useState(null)
const closeDropdown = useCallback(() => setDropdown(null), [])
const setupHyperlinkDetection = useCallback(() => {
const root = containerRef.current
if (!root) return undefined
const onRootClick = (e) => {
const target = e.target
if (!target) return
// Check if the clicked element is a hyperlink inside PDF layers
const anchor = target.closest("a[href]")
if (!anchor || !root.contains(anchor)) return
const layer = anchor.closest('[data-rp*="textLayer"], [data-rp*="annotationLayer"]')
if (!layer) return
// Exclusion: skip when href is empty
const hrefAttr = anchor.getAttribute("href") || "";
if (hrefAttr === "") {
return;
}
// Prevent default link behavior and show custom dropdown
e.preventDefault()
e.stopPropagation()
const rect = anchor.getBoundingClientRect()
setDropdown({
href: anchor.href,
x: rect.left + window.scrollX,
y: rect.bottom + window.scrollY + 5,
})
}
// Capture clicks inside the viewer
root.addEventListener("click", onRootClick, true)
return () => {
root.removeEventListener("click", onRootClick, true)
}
}, [])
useEffect(() => {
const cleanup = setupHyperlinkDetection()
return typeof cleanup === "function" ? cleanup : undefined
}, [setupHyperlinkDetection])
return (
<div ref={containerRef}>
{/* PDF Viewer */}
<RPProvider src="https://cdn.codewithmosh.com/image/upload/v1721763853/guides/web-roadmap.pdf">
<RPDefaultLayout style={{ width: "100%", height: "600px" }}>
<RPPages />
</RPDefaultLayout>
</RPProvider>
{/* Custom dropdown menu when clicking a hyperlink */}
{dropdown && (
<HyperlinkDropdown dropdown={dropdown} onClose={closeDropdown} />
)}
</div>
)
}
export default AppPdfViewer

Screenshot 2568-08-25 at 13.37.29

Example: Accessing topBarRight Element to Prepend a Button

Section titled “Example: Accessing topBarRight Element to Prepend a Button”
  1. Create a button element

    const buttonElement = useMemo(() => {
    const button = document.createElement('button')
    button.innerHTML = 'N'
    return button
    }, [])
  2. Get the element of the right section of the top bar container

    const topBarRight = ref.current?.querySelector('[data-rp="topBarRight"]')
  3. Add a button element to the element

    topBarRight?.prepend(buttonElement)
  4. Remove the button element when the component is cleaned up

    const cleanupOnLoaded = useCallback(() => {
    if (buttonElement) {
    buttonElement.remove()
    }
    }, [buttonElement])

    Remark: Use this function for dev mode due to the useEffect’s caveats

Putting everything together

import { useRef, useCallback, useMemo } from 'react'
import { RPConfig, RPProvider, RPDefaultLayout, RPPages } from '@pdf-viewer/react'
export const AppPdfViewer = () => {
const ref = useRef(null)
// Create the button element.
const buttonElement = useMemo(() => {
const button = document.createElement('button')
button.innerHTML = 'N'
return button
}, [])
const handleLoaded = useCallback(() => {
// Get the element of React PDF.
const topBarRight = ref.current?.querySelector('[data-rp="topBarRight"]')
// Add the button element to the top bar.
topBarRight?.prepend(buttonElement)
}, [buttonElement])
// Remove the button element when the component is cleaned up.
const cleanupOnLoaded = useCallback(() => {
if (buttonElement) {
buttonElement.remove()
}
}, [buttonElement])
return (
<RPConfig licenseKey="YOUR_LICENSE_KEY">
<RPProvider src="https://cdn.codewithmosh.com/image/upload/v1721763853/guides/web-roadmap.pdf">
<RPDefaultLayout ref={ref} onLoaded={handleLoaded} cleanupOnLoaded={cleanupOnLoaded}>
<RPPages />
</RPDefaultLayout>
</RPProvider>
</RPConfig>
)
}
An image of how React PDF displays with customized topRightBar element

Because More Options dropdown is not visible by default, you will need to use MutationObserver API to monitor changes of a specific DOM element.

An example: Accessing moreOptionsDropdown Element to Prepend a Button

  1. Define Mutation Observer Configuration

    const config = {
    attributes: false,
    childList: true,
    subtree: false
    }
    let observer
  2. Attach a Button to the Dropdown using Observer

    const handleMoreOptionsMount = useCallback(
    (elemContainer) => {
    const observerCallback = () => {
    const moreOptions = elemContainer.querySelector('[data-rp="moreOptionsDropdown"]')
    if (moreOptions) {
    moreOptions?.prepend(buttonElement)
    }
    }
    observer = new MutationObserver(observerCallback)
    observer.observe(elemContainer, config)
    },
    [buttonElement]
    )
  3. Cleanup the Observer on Unmount

    useEffect(() => {
    return () => observer?.disconnect()
    }, [])

Putting everything together

import { useRef, useCallback, useMemo, useEffect } from 'react'
import { RPConfig, RPProvider, RPDefaultLayout, RPPages } from '@pdf-viewer/react'
// Options for the Observer
const config = {
attributes: false,
childList: true,
subtree: false
}
let observer
export const AppPdfViewer = () => {
const ref = useRef(null)
// Create the button element
const buttonElement = useMemo(() => {
const button = document.createElement('button')
button.innerHTML = 'N'
return button
}, [])
const handleMoreOptionsMount = useCallback(
(elemContainer) => {
const observerCallback = () => {
const moreOptions = elemContainer.querySelector('[data-rp="moreOptionsDropdown"]')
if (moreOptions) {
moreOptions?.prepend(buttonElement)
}
}
observer = new MutationObserver(observerCallback)
observer.observe(elemContainer, config)
},
[buttonElement]
)
const handleLoaded = useCallback(() => {
const elemContainer = ref.current?.querySelector('[data-rp="container"]')
if (elemContainer) {
handleMoreOptionsMount(elemContainer)
}
}, [handleMoreOptionsMount])
// Remove the button element when the component is cleaned up
const cleanupOnLoaded = useCallback(() => {
if (buttonElement) {
buttonElement.remove()
}
}, [buttonElement])
// Cleanup the Observer on Unmount
useEffect(() => {
return () => observer?.disconnect()
}, [])
return (
<RPConfig licenseKey="YOUR_LICENSE_KEY">
<RPProvider src="https://cdn.codewithmosh.com/image/upload/v1721763853/guides/web-roadmap.pdf">
<RPDefaultLayout ref={ref} onLoaded={handleLoaded} cleanupOnLoaded={cleanupOnLoaded}>
<RPPages />
</RPDefaultLayout>
</RPProvider>
</RPConfig>
)
}
An image of how React PDF displays with customized moreOptionsDropdown element