Official library to use Popper on React projects
—
Programmatic hook interface for low-level control over Popper instances without component wrappers. The usePopper hook provides direct access to Popper.js functionality with React integration, ideal for custom implementations and advanced use cases.
Provides programmatic control over Popper instances with automatic lifecycle management and state synchronization.
/**
* Low-level hook for programmatic control over Popper instances
* @param referenceElement - The reference element to position relative to
* @param popperElement - The popper element to position
* @param options - Configuration options for the Popper instance
* @returns Object containing styles, attributes, state, and control functions
*/
function usePopper<Modifiers>(
referenceElement?: Element | PopperJS.VirtualElement | null,
popperElement?: HTMLElement | null,
options?: Omit<Partial<PopperJS.Options>, 'modifiers'> & {
createPopper?: typeof PopperJS.createPopper;
modifiers?: ReadonlyArray<Modifier<Modifiers>>;
}
): UsePopperResult;
interface UsePopperResult {
/** Computed styles for all positioned elements (popper, arrow, etc.) */
styles: { [key: string]: React.CSSProperties };
/** HTML attributes for elements (data-popper-placement, etc.) */
attributes: { [key: string]: { [key: string]: string } | undefined };
/** Current Popper.js state object (null if not initialized) */
state: PopperJS.State | null;
/** Function to manually update popper positioning */
update: PopperJS.Instance['update'] | null;
/** Function to force immediate positioning update */
forceUpdate: PopperJS.Instance['forceUpdate'] | null;
}The usePopper hook accepts all standard Popper.js options plus React-specific enhancements:
interface UsePopperOptions {
/** Preferred placement for the popper element */
placement?: PopperJS.Placement;
/** Positioning strategy - 'absolute' or 'fixed' */
strategy?: PopperJS.PositioningStrategy;
/** Array of Popper.js modifiers */
modifiers?: ReadonlyArray<Modifier<any>>;
/** Callback fired after first positioning update */
onFirstUpdate?: (state: Partial<PopperJS.State>) => void;
/** Custom createPopper function (for custom Popper builds) */
createPopper?: typeof PopperJS.createPopper;
}Usage Examples:
import React from "react";
import { usePopper } from "react-popper";
// Basic hook usage
function BasicHookExample() {
const [referenceElement, setReferenceElement] = React.useState(null);
const [popperElement, setPopperElement] = React.useState(null);
const [arrowElement, setArrowElement] = React.useState(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: "top",
modifiers: [
{ name: "arrow", options: { element: arrowElement } },
{ name: "offset", options: { offset: [0, 8] } },
],
});
return (
<>
<button ref={setReferenceElement}>Reference Button</button>
<div
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
Popper content
<div ref={setArrowElement} style={styles.arrow} />
</div>
</>
);
}
// Advanced hook with state management
function AdvancedHookExample() {
const [referenceElement, setReferenceElement] = React.useState(null);
const [popperElement, setPopperElement] = React.useState(null);
const [visible, setVisible] = React.useState(false);
const { styles, attributes, state, update, forceUpdate } = usePopper(
referenceElement,
popperElement,
{
placement: "bottom-start",
strategy: "fixed",
modifiers: [
{
name: "preventOverflow",
options: {
boundary: "viewport",
padding: 8,
},
},
{
name: "flip",
options: {
fallbackPlacements: ["top-start", "bottom-end", "top-end"],
},
},
],
onFirstUpdate: (state) => {
console.log("Initial popper state:", state);
},
}
);
// Manually update positioning when needed
const handleUpdate = async () => {
if (update) {
const newState = await update();
console.log("Updated state:", newState);
}
};
// Force update immediately
const handleForceUpdate = () => {
if (forceUpdate) {
const newState = forceUpdate();
console.log("Force updated state:", newState);
}
};
return (
<div>
<button
ref={setReferenceElement}
onClick={() => setVisible(!visible)}
>
Toggle Popper
</button>
{visible && (
<div
ref={setPopperElement}
style={{
...styles.popper,
background: "white",
border: "1px solid #ccc",
borderRadius: "4px",
padding: "12px",
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
zIndex: 1000,
}}
{...attributes.popper}
>
<div>Advanced Popper Content</div>
{state && (
<div style={{ fontSize: "12px", color: "#666", marginTop: "8px" }}>
<div>Placement: {state.placement}</div>
<div>Strategy: {state.options.strategy}</div>
</div>
)}
<div style={{ marginTop: "8px" }}>
<button onClick={handleUpdate}>Update</button>
<button onClick={handleForceUpdate}>Force Update</button>
</div>
</div>
)}
</div>
);
}
// Custom createPopper function
function CustomPopperExample() {
const [referenceElement, setReferenceElement] = React.useState(null);
const [popperElement, setPopperElement] = React.useState(null);
// Custom Popper configuration
const customCreatePopper = React.useCallback(
(reference, popper, options) => {
// Add custom logic or use a custom Popper build
return PopperJS.createPopper(reference, popper, {
...options,
// Add default modifiers or override behavior
modifiers: [
...options.modifiers,
{
name: "customModifier",
enabled: true,
phase: "beforeWrite",
fn: ({ state }) => {
// Custom positioning logic
console.log("Custom modifier executed:", state);
},
},
],
});
},
[]
);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
createPopper: customCreatePopper,
placement: "top",
});
return (
<>
<button ref={setReferenceElement}>Custom Popper</button>
<div
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
Custom Popper Content
</div>
</>
);
}The usePopper hook supports virtual elements for positioning relative to coordinates rather than DOM elements:
interface VirtualElement {
getBoundingClientRect(): DOMRect;
contextElement?: Element;
}Virtual Element Example:
function VirtualElementExample() {
const [popperElement, setPopperElement] = React.useState(null);
// Create virtual element at mouse position
const virtualElement = React.useMemo(() => ({
getBoundingClientRect: () => ({
width: 0,
height: 0,
top: 100,
right: 100,
bottom: 100,
left: 100,
x: 100,
y: 100,
toJSON: () => ({}),
}),
}), []);
const { styles, attributes } = usePopper(virtualElement, popperElement, {
placement: "bottom",
});
return (
<div
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
Positioned at coordinates (100, 100)
</div>
);
}The usePopper hook automatically manages Popper instance lifecycle:
The hook handles various edge cases gracefully:
Memoize complex options:
const options = React.useMemo(() => ({
placement: "top",
modifiers: [{ name: "offset", options: { offset: [0, 8] } }],
}), []);Handle loading states:
const { styles, attributes, state } = usePopper(ref1, ref2, options);
if (!state) {
return <div>Loading...</div>;
}Use consistent element references:
// ✅ Stable references
const [refElement, setRefElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
// ❌ Avoid creating new references on each render
const refElement = useRef(null).current;Combine with other hooks for complex behavior:
function useTooltip() {
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
const [visible, setVisible] = useState(false);
const popper = usePopper(referenceElement, popperElement, {
placement: "top",
});
return {
...popper,
visible,
setVisible,
setReferenceElement,
setPopperElement,
};
}Install with Tessl CLI
npx tessl i tessl/npm-react-popper