UnflowUI
Components

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

PropTypeDefaultDescription
anchorElElement | VirtualElementrequiredThe element the Popper positions relative to
openbooleanfalseWhether the Popper is visible
placementPlacement'bottom-start'Position relative to anchor (12 options from @floating-ui)
offsetnumber4Pixel gap between anchor and Popper
keepMountedbooleanfalseKeep the Popper in the DOM when closed
onClickAway() => voidCalled when clicking outside the Popper
clickAwayIgnoreElementElement 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.