Introducing PrimeReact v11-alpha 🎉Discover Now

usePopover

Hook that manages popover open state, outside click dismissal, escape key handling, and focus management.

basic-demo

Usage#

import { usePopover } from '@primereact/headless/popover';
import { usePortal } from '@primereact/headless/portal';
import { usePositioner } from '@primereact/headless/positioner';
import { createPortal } from 'react-dom';
 
const { triggerProps, popupProps, positionerProps, arrowProps, closeProps, state } = usePopover();
const portal = usePortal();
 
usePositioner({
    anchor: state.anchorElement,
    content: state.positionerElement,
    arrow: state.arrowElement,
    side: 'bottom',
    flip: true,
    shift: true
});
 
return (
    <>
        <button {...triggerProps}></button>;
        {
            portal.state.mounted &&
                state.open &&
                createPortal(
                    <div {...positionerProps}>
                        <div {...popupProps}>
                            <div {...arrowProps} />
                            <button {...closeProps}></button>
                        </div>
                    </div>,
                    document.body
                );
        }
    </>
);

usePopover manages open/close state, outside click dismissal, escape key handling, and optional focus trapping. Use usePositioner for anchor-relative positioning and usePortal with createPortal for body portaling — see Primitive for a component-based API.

Features#

  • triggerProps, popupProps, positionerProps, closeProps, and arrowProps return spread-ready props including ref callbacks for each element
  • state.open for conditional rendering of the portal content
  • setOpen(open, event) for imperative open/close control
  • Optional focus trapping with trapped prop for modal-like behavior

Behavior#

Portal Rendering#

usePopover manages open state and element refs but does not handle DOM portaling. Use usePortal to detect mount state and createPortal from react-dom to render into document.body.

import { usePortal } from '@primereact/headless/portal';
import { createPortal } from 'react-dom';
 
const portal = usePortal();
 
{
    portal.state.mounted &&
        state.open &&
        createPortal(
            <div {...positionerProps}>
                <div {...popupProps}>...</div>
            </div>,
            document.body
        );
}

Arrow#

Spread arrowProps on an element inside the popup and pass state.arrowElement to usePositioner. The positioner sets CSS custom properties (--placer-arrow-x, --placer-arrow-y) on the arrow for positioning per side.

const { arrowProps, state } = usePopover();
 
usePositioner({ anchor: state.anchorElement, content: state.positionerElement, arrow: state.arrowElement });
 
<div {...popupProps}>
    <div {...arrowProps} />
</div>;

Style each side using positioner.state.actualSide:

[data-scope='popover'][data-part='arrow'] {
    position: absolute;
    width: 0.5rem;
    height: 0.5rem;
    border-left: 1px solid var(--p-content-border-color);
    border-top: 1px solid var(--p-content-border-color);
}
[data-part='arrow'][data-side='bottom'] {
    top: -0.25rem;
    left: var(--placer-arrow-x);
    transform: translateX(-50%) rotate(45deg);
}
[data-part='arrow'][data-side='top'] {
    bottom: -0.25rem;
    left: var(--placer-arrow-x);
    transform: translateX(-50%) rotate(225deg);
}

Default Open#

Set defaultOpen for uncontrolled popover state.

const popover = usePopover({ defaultOpen: true });

Controlled#

Pass open and onOpenChange for controlled usage.

const [isOpen, setIsOpen] = React.useState(false);
 
const popover = usePopover({
    open: isOpen,
    onOpenChange: (e) => setIsOpen(e.open)
});

Close on Escape#

Set closeOnEscape to close the popover when the Escape key is pressed. Enabled by default.

const popover = usePopover({ closeOnEscape: true });

Focus Trapping#

Set trapped to trap focus within the popover content, preventing focus from leaving until the popover is closed.

const popover = usePopover({ trapped: true });

When trapped is enabled, render the focus trap sentinels using focusTrap.firstHiddenElementRef and focusTrap.lastHiddenElementRef around the popup content.

Auto Focus#

Set autoFocus to false to prevent automatically focusing the first focusable element when the popover opens.

const popover = usePopover({ autoFocus: false });

Custom Styling with Data Attributes#

[data-scope='popover'][data-part='trigger'][data-positioner-open] {
    background-color: light-dark(var(--p-surface-100), var(--p-surface-700));
}
[data-scope='popover'][data-part='popup'][data-open] {
    opacity: 1;
}

API#

usePopover#

NameTypeDefault
defaultOpenbooleanundefined
Whether the popover is open by default.
openbooleanundefined
Whether the popover is open.
trappedbooleanfalse
When enabled, focus is trapped within the popover (modal behavior). When disabled, focus leaving the popover closes it (non-modal behavior).
autoFocusbooleantrue
Whether to focus the first focusable element when the popover is opened.
closeOnEscapebooleantrue
Specifies if pressing escape key should hide the dialog.
onOpenChange(event: usePopoverOpenChangeEvent) => voidundefined
Callback to invoke when the open state changes.
appendTo"body" | HTMLElement | "self"'body'
DOM element or CSS selector to append the overlay to.

Accessibility#

See Popover Primitive for WAI-ARIA compliance details and keyboard support.