Popover
Open a floating panel from a trigger on click or hover.
Popover is a client component that opens a floating panel from a trigger, on click or on hover. Use it for account menus, filters and short help text. Click mode is built on the Headless UI Popover; hover mode uses a custom implementation. Both use @floating-ui/react for positioning and motion/react for the enter and exit animation.
Import#
import { Popover } from "@reactberry/system/blocks";
Usage#
import { Popover } from "@reactberry/system/blocks";
import { Box, Button, Text } from "@reactberry/system/elements";
export default function AccountMenu() {
return (
<Popover
trigger={<Text>Account</Text>}
placement="bottom start"
panelProps={{ width: "12rem" }}
>
{({ close }) => (
<Box display="flex" flexDirection="column" gap="xxsmall">
<Button variant="ghost" onClick={close}>
<Text>Profile</Text>
</Button>
<Button variant="ghost" onClick={close}>
<Text>Sign out</Text>
</Button>
</Box>
)}
</Popover>
);
}
Examples#
Hover mode#
Set triggerMode="hover" for help text that opens on pointer hover. hoverDelay sets the delay before opening and closing.
Custom trigger#
Use renderTrigger to render your own trigger element instead of the default ghost Button.
API#
Popover#
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | ((props: { close: () => void; open: boolean; }) => ReactNode) | — | Panel content. The render-function form receives |
trigger | ReactNode | — | Trigger content. Wrapped in a ghost |
placement | string | bottom end | Floating UI placement. Both |
triggerProps | any | — | Props spread onto the default trigger |
containerProps | any | — | Props spread onto the outer container |
panelProps | any | — | Props spread onto the panel, overriding its defaults ( |
triggerMode | "hover""click" | "click" | How the panel opens. |
hoverDelay | number | 200 | Delay in ms before opening or closing in hover mode. |
usePortal | boolean | false | Renders the panel in a Headless UI |
scrollContainer | HTMLElement | null | null | Element whose scroll events close the popover. Click mode falls back to |
renderTrigger | ((props: { ref: (node: HTMLElement | null) => void; onClick?: (() => void); }) => ReactNode) | — | Custom trigger renderer (click mode only). The result is wrapped in a |
onOpenChange | ((isOpen: boolean) => void) | — | Called when the open state changes (click mode only). |
The panel also gets shape="rounded" and $shadow="medium". Props not listed above are not spread.
Keyboard#
Click mode only; keyboard handling comes from Headless UI Popover.
| Key | Action |
|---|---|
Enter | Toggles the panel when the trigger has focus. |
Space | Toggles the panel when the trigger has focus. |
Escape | Closes the panel. |
Accessibility#
- In click mode the trigger is a Headless UI
PopoverButtonand the panel aPopoverPanel, which managearia-expanded,aria-controlsand focus. - Hover mode renders a plain
divthat opens onmouseenterand closes onmouseleave. It cannot be opened with the keyboard or by touch, so don't put essential content in it.
Notes#
- Click mode closes on outside click and Escape via Headless UI. It also closes on scroll of
scrollContainerorwindow, except while focus is inside the panel (so focusing an input on mobile does not dismiss it). - Hover mode closes on scroll only when
scrollContaineris set. When portalled, hovering the panel keeps it open. - In hover mode,
renderTrigger,triggerProps, andonOpenChangeare ignored, and function children are not called. - The
refpassed torenderTriggeris a no-op; the wrappingPopoverButtonreceives the positioning ref instead. onOpenChangeis called while the popover renders, not in an effect.- The panel is offset 4px from the trigger and uses the Floating UI
flipandshift(8px padding) middleware.