usePositioner
Hook that positions a floating element relative to an anchor with automatic flip, shift, and arrow support.
Usage#
import { usePositioner } from '@primereact/headless/positioner';
const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);
const [contentEl, setContentEl] = React.useState<HTMLDivElement | null>(null);
const positioner = usePositioner({
anchor: anchorEl,
content: contentEl,
side: 'bottom',
sideOffset: 8,
flip: true
});
return (
<>
<button ref={setAnchorEl}></button>
<div ref={setContentEl}></div>
</>
);usePositioner computes placement coordinates for a floating element relative to an anchor, with automatic overflow handling — used internally by Popover.Positioner, Select.Positioner, and similar sub-components.
Features#
- Dual-mode positioning: native CSS Anchor Positioning API with automatic JavaScript fallback for older browsers
sideandaligncontrol preferred placement;flipandshifthandle viewport overflowstate.actualSideandstate.actualAlignreflect the computed placement after flip/shift- Arrow-aware offset calculation:
sideOffsetauto-derives from arrow element size when not explicitly set - CSS custom properties for arrow positioning (
--placer-arrow-x,--placer-arrow-y,--placer-arrow-left,--placer-arrow-top) updatePlacement()for imperative recalculation- Automatic z-index management via
autoZIndex - RAF-throttled scroll/resize listeners with ResizeObserver for both anchor and content
Behavior#
Side and Align#
Set side to control which edge the content appears on. Set align to control cross-axis alignment.
usePositioner({ anchor, content, side: 'top', align: 'start' });Supported values: side accepts 'top', 'right', 'bottom', 'left'. align accepts 'start', 'center', 'end'.
Flip#
Set flip to automatically move content to the opposite side when there is insufficient space. Enabled by default.
usePositioner({ anchor, content, side: 'top', flip: true });Shift#
Set shift to slide content along the cross-axis to stay within the viewport boundary. Enabled by default.
usePositioner({ anchor, content, shift: true });Side Offset and Align Offset#
Set sideOffset to add distance between the anchor and content. Set alignOffset to shift along the cross-axis.
usePositioner({ anchor, content, sideOffset: 12, alignOffset: 4 });When sideOffset is not set and an arrow element is provided, the offset is automatically calculated from the arrow size.
Arrow Positioning#
Pass an arrow element to enable arrow-aware positioning. The hook sets CSS custom properties on the content element for arrow placement.
usePositioner({ anchor, content, arrow: arrowEl });Available CSS custom properties on the content element:
var(--placer-arrow-x) /* center X of arrow */
var(--placer-arrow-y) /* center Y of arrow */
var(--placer-arrow-left) /* left offset of arrow */
var(--placer-arrow-top) /* top offset of arrow */
var(--transform-origin) /* transform origin pointing to anchor */Hide When Detached#
Set hideWhenDetached to hide the content when the anchor scrolls out of the boundary.
usePositioner({ anchor, content, hideWhenDetached: true });Strategy#
Set strategy to 'absolute' for absolute positioning relative to the offset parent. Defaults to 'fixed'.
usePositioner({ anchor, content, strategy: 'absolute' });Boundary#
Pass a boundary element to constrain placement within a specific container (JavaScript fallback mode only).
usePositioner({ anchor, content, boundary: containerEl });Placement Change Callback#
Use onPlacementChange to react when the actual placement changes due to flip or shift.
usePositioner({
anchor,
content,
onPlacementChange: ({ side, align }) => console.log(side, align)
});Imperative Update#
Call updatePlacement() to force a recalculation when content dimensions change dynamically.
const positioner = usePositioner({ anchor, content });
positioner.updatePlacement();Custom Styling with Data Attributes#
The hook sets data-side and data-align on the content (and arrow) element for placement-aware CSS.
[data-side='bottom'] {
margin-top: 4px;
}
[data-side='top'] {
margin-bottom: 4px;
}
[data-side='bottom'][data-align='start'] {
/* bottom-start placement */
}API#
usePositioner#
| Name | Type | Default |
|---|---|---|
side | SideType | — |
| The side of the positioner. | ||
align | AlignType | — |
| The align of the positioner. | ||
sideOffset | number | — |
| The side offset of the positioner. | ||
alignOffset | number | — |
| The align offset of the positioner. | ||
flip | boolean | — |
| Whether to flip the positioner. | ||
shift | boolean | — |
| Whether to shift the positioner. | ||
hideWhenDetached | boolean | — |
| Whether to hide the positioner when detached. | ||
strategy | "fixed" | "absolute" | 'fixed' |
| The positioning strategy. | ||
boundary | HTMLElement | — |
| The boundary element that constrains the positioner placement. Used in JS fallback mode only; CSS Anchor mode uses the containing block. | ||
anchor | HTMLElement | — |
| The anchor element. | ||
content | HTMLDivElement | — |
| The content element. | ||
arrow | HTMLDivElement | — |
| The arrow element. | ||
autoZIndex | boolean | true |
| Whether to automatically manage layering. | ||
baseZIndex | number | 0 |
| Base zIndex value to use in layering. | ||
onPlacementChange | (placement: { side: SideType; align: AlignType }) => void | — |
| Callback invoked when the actual placement changes due to flip or shift. | ||
Accessibility#
usePositioner is a structural positioning utility. It does not introduce ARIA roles or keyboard interactions. Accessibility concerns are handled by the consumer component (e.g., Popover, Tooltip, Select).