Viewer Element Usage
React PDF provides data-rp
attributes to access specific elements so that you can customize the viewer easily.
Attribute | description |
---|---|
data-rp="moreOptionsDropdown" | The dropdown container of the More Options menu |
data-rp="page-{pageNumber}" | Page element Remark: Replace pageNumber with a specific value (e.g., data-rp="page-21" ) |
data-rp="page-{pageNumber}-canvas" | Page canvas element Remark: Replace pageNumber with a specific value (e.g., data-rp="page-21-canvas" ) |
data-rp="page-{pageNumber}-textLayer" | Page text layer element Remark: Replace pageNumber with a specific value (e.g., data-rp="page-21-textLayer" ) |
data-rp="page-{pageNumber}-annotationLayer" | Annotation layer element Remark: Replace pageNumber 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.
Example: Access the page-{pageNumber}-annotationLayer
Element to Detect a Link in a PDF Page
Section titled “Example: Access the page-{pageNumber}-annotationLayer Element to Detect a Link in a PDF Page”-
Create a state to store the dropdown position and the link href
const [dropdown, setDropdown] = useState(null)const [dropdown, setDropdown] = useState<boolean | null>(null) -
Detect when a user clicks on a hyperlink inside the PDF Viewer
const setupHyperlinkDetection = useCallback(() => {const root = containerRef.currentif (!root) return undefinedconst onRootClick = (e) => {const target = e.targetif (!target) return// Check if the clicked element is a hyperlink inside PDF layersconst anchor = target.closest("a[href]")if (!anchor || !root.contains(anchor)) returnconst layer = anchor.closest('[data-rp*="textLayer"], [data-rp*="annotationLayer"]')if (!layer) return// Exclusion: skip when href is emptyconst hrefAttr = anchor.getAttribute("href") || "";if (hrefAttr === "") {return;}// Prevent default link behavior and show custom dropdowne.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 viewerroot.addEventListener("click", onRootClick, true)return () => {root.removeEventListener("click", onRootClick, true)}}, [])const setupHyperlinkDetection = useCallback((): (() => void) | undefined => {const root = containerRef.currentif (!root) return undefinedconst onRootClick = (e: MouseEvent) => {const target = e.target as HTMLElement | null;if (!target) return// Check if the clicked element is a hyperlink inside PDF layersconst anchor = target.closest<HTMLAnchorElement>("a[href]")if (!anchor || !root.contains(anchor)) returnconst layer = anchor.closest('[data-rp*="textLayer"], [data-rp*="annotationLayer"]')if (!layer) return// Exclusion: skip when href is emptyconst hrefAttr = anchor.getAttribute("href") || "";if (hrefAttr === "") {return;}// Prevent default link behavior and show custom dropdowne.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 viewerroot.addEventListener("click", onRootClick, true)return () => {root.removeEventListener("click", onRootClick, true)}}, []) -
Create a Dropdown UI to show actions when the user clicks a hyperlink
const HyperlinkDropdown = ({ dropdown, onClose }) => {return (<divstyle={{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",}}><buttonaria-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>)}interface DropdownPosition {href: string;x: number;y: number;}interface HyperlinkDropdownProps {dropdown: DropdownPosition;onClose: () => void;}const HyperlinkDropdown: React.FC<HyperlinkDropdownProps> = ({ dropdown, onClose }) => {return (<divstyle={{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",}}><buttonaria-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>)} -
Use useEffect to attach the event detection when the component is mounted
useEffect(() => {const cleanup = setupHyperlinkDetection()return typeof cleanup === "function" ? cleanup : undefined}, [setupHyperlinkDetection])useEffect(() => {return setupHyperlinkDetection();}, [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
import React, { useCallback, useEffect, useRef, useState } from "react";import { RPProvider, RPDefaultLayout, RPPages,} from "@pdf-viewer/react";
interface DropdownPosition { href: string; x: number; y: number;}
interface HyperlinkDropdownProps { dropdown: DropdownPosition; onClose: () => void;}
const HyperlinkDropdown: React.FC<HyperlinkDropdownProps> = ({ 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: React.FC = () => { // Reference to the viewer container const containerRef = useRef<HTMLDivElement | null>(null);
// State for the hyperlink dropdown const [dropdown, setDropdown] = useState<DropdownPosition | null>(null);
const closeDropdown = useCallback(() => setDropdown(null), []);
const setupHyperlinkDetection = useCallback((): (() => void) | undefined => { const root = containerRef.current; if (!root) return;
const onRootClick = (e: MouseEvent) => { const target = e.target as HTMLElement | null; if (!target) return;
// Check if the clicked element is a hyperlink inside PDF layers const anchor = target.closest<HTMLAnchorElement>("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(() => { return setupHyperlinkDetection(); }, [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;
Example: Accessing topBarRight
Element to Prepend a Button
Section titled “Example: Accessing topBarRight Element to Prepend a Button”-
Create a button element
const buttonElement = useMemo(() => {const button = document.createElement('button')button.innerHTML = 'N'return button}, [])const buttonElement = useMemo(() => {const button = document.createElement('button')button.innerHTML = 'N'return button}, []) -
Get the element of the right section of the top bar container
const topBarRight = ref.current?.querySelector('[data-rp="topBarRight"]')const topBarRight = ref.current?.querySelector('[data-rp="topBarRight"]') -
Add a button element to the element
topBarRight?.prepend(buttonElement)topBarRight?.prepend(buttonElement) -
Remove the button element when the component is cleaned up
const cleanupOnLoaded = useCallback(() => {if (buttonElement) {buttonElement.remove()}}, [buttonElement])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> )}
import { useRef, useCallback, useMemo } from 'react'import { RPConfig, RPProvider, RPDefaultLayout, RPPages } from '@pdf-viewer/react'
export const AppPdfViewer = () => { const ref = useRef<HTMLDivElement>(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> )}

Select moreOptionsDropdown
Element
Section titled “Select moreOptionsDropdown 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
-
Define Mutation Observer Configuration
const config = {attributes: false,childList: true,subtree: false}let observerconst config = {attributes: false,childList: true,subtree: false}let observer: MutationObserver -
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])const handleMoreOptionsMount = useCallback((elemContainer: Element) => {const observerCallback = () => {const moreOptions = elemContainer.querySelector('[data-rp="moreOptionsDropdown"]')if (moreOptions) {moreOptions?.prepend(buttonElement)}}observer = new MutationObserver(observerCallback)observer.observe(elemContainer, config)},[buttonElement]) -
Cleanup the Observer on Unmount
useEffect(() => {return () => observer?.disconnect()}, [])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 Observerconst 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> )}
import { useRef, useCallback, useMemo, useEffect } from 'react'import { RPConfig, RPProvider, RPDefaultLayout, RPPages } from '@pdf-viewer/react'
// Options for the Observerconst config = { attributes: false, childList: true, subtree: false}
let observer: MutationObserver
export const AppPdfViewer = () => { const ref = useRef<HTMLDivElement>(null)
// Create the button element const buttonElement = useMemo(() => { const button = document.createElement('button') button.innerHTML = 'N' return button }, [])
const handleMoreOptionsMount = useCallback( (elemContainer: Element) => { 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> )}
