Popper
A floating position primitive powered by @floating-ui/react-dom. The foundation for Dropdown and any anchored overlay.
Overview
Popper positions a floating element relative to an anchor. It wraps @floating-ui/react-dom and manages the CSS visibility toggle (opacity 0/1), click-away detection via ClickAwayEvent, and optional persistent mounting via keepMounted.
Use Popper directly when you need a custom floating overlay. For action lists, use Dropdown or ActionsDropdown which build on top of Popper.
Import
import { Popper } from '@unflow/ui/components/Popper';Props
| Prop | Type | Default | Description |
|---|---|---|---|
anchorEl | Element | VirtualElement | required | The element the Popper positions relative to |
open | boolean | false | Whether the Popper is visible |
placement | Placement | 'bottom-start' | Position relative to anchor (12 options from @floating-ui) |
offset | number | 4 | Pixel gap between anchor and Popper |
keepMounted | boolean | false | Keep the Popper in the DOM when closed |
onClickAway | () => void | — | Called when clicking outside the Popper |
clickAwayIgnore | Element | — | Element to exclude from click-away detection |
All standard HTMLDivElement props are accepted.
Placement options
top-start top top-end
left-start right-start
left right
left-end right-end
bottom-start bottom bottom-end<Popper anchorEl={anchor} placement="bottom-start" open={isOpen}>
{/* content */}
</Popper>VirtualElement
anchorEl accepts a VirtualElement — an object with a getBoundingClientRect() method. This lets you position a Popper at an arbitrary point (e.g. the cursor position for a context menu):
const virtualAnchor: VirtualElement = {
getBoundingClientRect: () => ({
x: mouseX,
y: mouseY,
width: 0,
height: 0,
top: mouseY,
right: mouseX,
bottom: mouseY,
left: mouseX,
}),
};
<Popper anchorEl={virtualAnchor} open={isOpen}>
<ContextMenu />
</Popper>Examples
Basic tooltip-style Popper
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
<button ref={el => setAnchorEl(el)} onClick={() => setOpen(true)}>
Hover me
</button>
{anchorEl && (
<Popper
anchorEl={anchorEl}
open={isOpen}
placement="top"
onClickAway={() => setOpen(false)}
clickAwayIgnore={anchorEl}
>
<div className="bg-grey-900 text-white text-xs px-2 py-1 rounded">
Tooltip text
</div>
</Popper>
)}Persistent Popper (keepMounted)
Use keepMounted={true} when the Popper contains stateful content (search input, form) that should survive close/open cycles:
<Popper anchorEl={anchor} open={isOpen} keepMounted>
<SearchDropdown />
</Popper>When closed, the Popper is still in the DOM but invisible (opacity-0). This means the search input retains its value between opens.
Implementation notes
Popper uses CSS opacity for visibility rather than conditional rendering. This means the Popper's DOM node is always present after first mount (even when keepMounted={false}), which avoids a brief layout flash on first open. The keepMounted prop controls whether the node is mounted at all before the first open={true}.
Position updates are computed by @floating-ui/react-dom's computePosition after every render that changes open, anchorEl, or placement.
Accessibility
Popper has no built-in ARIA attributes. Depending on the content, you may need:
<Popper anchorEl={anchor} open={isOpen} role="dialog" aria-modal="true">
{/* modal content */}
</Popper>For dropdown menus, Dropdown handles role="menu" and role="menuitem" via ActionsDropdown.
Scrollable
A scroll container with a consistently styled custom scrollbar. Supports vertical, horizontal, or both-axis scrolling with optional smooth scroll and persistent scrollbar gutter.
ActionCard
A full-width list item that acts as a button or toggle. Used inside dropdowns and command palettes for keyboard-navigable action lists.