Popover
An anchored panel that floats beside its trigger, built on Base UI's popover primitive.
Import
import { Popover } from '@oztix/roadie-components/popover'
Examples
Default
A trigger opens a positioned panel. Popover.Content collapses the underlying Portal, Positioner, and Popup into a single element, and Popover.Arrow renders a pointer connecting the panel to its trigger.
Intents
Portaled popups sit outside the trigger's CSS cascade, so set the colour context directly on Popover.Content with the intent prop. Each intent fits a different message — info for inline help, success for confirmation, warning and danger for caution and destructive actions, and accent to highlight something new.
Position
A popover opens below its trigger by default and re-flips to stay in view. Set positionerProps on Popover.Content to control placement — side (top/right/bottom/left), align (start/center/end), and sideOffset.
Hover
Set openOnHover on Popover.Trigger (with an optional delay) so the panel opens on hover as well as click — ideal for previews and inline help. It still opens on focus and click, so keyboard users aren't locked out.
Composition
Popover is a compound component. Use Popover.Header, Popover.Body, and Popover.Footer to structure the panel, and Popover.Close to dismiss it.
With footer actions
A Popover.Footer holds a centered actions row — ideal for a lightweight confirm-deletion flow anchored to the trigger.
Guidelines
Use a popover for contextual content anchored to a control — menus, quick forms, and confirmations. It has no size prop, sizing to its content and anchor, and you set intent on Popover.Content, not the trigger, since portaled popups do not inherit the trigger's CSS cascade.
Name buttons by their action
Do
Label the confirming button with the specific verb and object the user is committing to. A clear action like "Save changes" lets people act without rereading the title, and for destructive flows naming the consequence — "Delete event" — leaves no doubt about what happens.
Don’t
Vague labels like "OK", "Yes", or "Submit" tell the user nothing on their own. They have to jump back to the title to work out what they are actually agreeing to.
Write a title that states the decision
Delete event?
Do
A title that names the affected object and the action — a clear question or noun phrase — tells the user exactly what is at stake before they read another word.
Are you sure?
Don’t
Vague titles that never name the affected object force the user to hunt through the body for context, and make them hesitate before acting.
Use one prominent action, the rest normal
Do
Keep popover actions size='sm', with one prominent emphasis='strong' accent button (or danger for a destructive confirm) and the rest normal or default. In a compact, anchored panel the primary path needs to read instantly.
Don’t
Never use emphasis='subtle' for footer actions — a barely-there button reads as disabled in a tight panel and the prominent path loses its contrast.
Reach for a dialog when the task blocks
Do
Use a popover for contextual content anchored to a control — menus, quick confirms, and lightweight panels that sit beside the trigger without taking over the page.
Don’t
Don't use a popover for tasks that demand focus or block the page — those need a backdrop and trapped focus, so use a Dialog instead.
Anchor to the side that fits the action
Do
Place the panel where it relates to the trigger — inline help to the right of a field, a brief confirmation top of the button that fired it — and let it re-flip to stay in view.
Don’t
Don't force a side that covers the trigger or the content the popover refers to, or that pushes the panel off-screen on smaller viewports.
Reserve hover for low-stakes content
Do
Use openOnHover for previews and inline help that carry no decision — profile cards, definitions, field hints. It still opens on focus and click, so keyboard users aren't excluded.
Don’t
Don't put confirmations or footer actions behind hover. A destructive or committing action needs a deliberate click, not a pointer that happens to pass over the trigger.
Accessibility
- Keyboard:
EnterorSpaceon the trigger opens the popover;Escapecloses it and returns focus to the trigger. - Default action: while the popover is open,
Enteractivates the strong primary action (theemphasis='strong'button), like a form's default submit. It is ignored while focus is on another button, a multi-line textarea, or editable content. - Focus: Focus moves into the popup when opened and is restored to the trigger on close.
- ARIA: The trigger and popup are wired together via Base UI, and
Popover.Title/Popover.Descriptionare announced as the accessible name and description. - Icon-only close: When the dismiss control shows only an icon, give it an explicit label — e.g.
<Popover.Close render={<IconButton aria-label='Close'><XIcon weight='bold' /></IconButton>} />— so screen readers announce its purpose.
API reference
Popover
Whether the popover is initially open. To render a controlled popover, use the `open` prop instead.
Defaults to false.
Whether the popover is currently open.
Event handler called when the popover is opened or closed.
Event handler called after any animations complete when the popover is opened or closed.
A ref to imperative actions. - `unmount`: When specified, the popover will not be unmounted when closed. Instead, the `unmount` function must be called to unmount the popover manually. Useful when the popover's animation is controlled by an external library. - `close`: Closes the dialog imperatively when called.
Determines if the popover enters a modal state when open. - `true`: user interaction is limited to the popover: document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the popover, but document page scroll is not locked and pointer interactions outside of it remain enabled. When `modal` is `true`, focus trapping is enabled only if `<Popover.Close>` is rendered inside `<Popover.Popup>`. It can be visually hidden with your own CSS if needed, such as Tailwind's `sr-only` utility. When `modal` is `'trap-focus'`, render `<Popover.Close>` inside `<Popover.Popup>` so touch screen readers can escape the popup.
Defaults to false.
ID of the trigger that the popover is associated with. This is useful in conjunction with the `open` prop to create a controlled popover. There's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).
ID of the trigger that the popover is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open popover.
A handle to associate the popover with a trigger. If specified, allows external triggers to control the popover's open state.
The content of the popover. This can be a regular React node or a render function that receives the `payload` of the active trigger.
Popover.Arrow
No additional props — forwards all standard HTML attributes to the underlying element.
Popover.Body
No additional props — forwards all standard HTML attributes to the underlying element.
Popover.Close
Inherited from NativeButtonProps
Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (e.g. `<div>`).
Defaults to true.
Popover.Content
Props forwarded to the underlying Positioner (side, align, sideOffset).
Inherited from PopoverPopupProps
Determines the element to focus when the popover is opened. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.
Determines the element to focus when the popover is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.
Popover.Description
No additional props — forwards all standard HTML attributes to the underlying element.
Popover.Footer
No additional props — forwards all standard HTML attributes to the underlying element.
Popover.Header
No additional props — forwards all standard HTML attributes to the underlying element.
Popover.Popup
Inherited from PopoverPopupProps
Determines the element to focus when the popover is opened. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.
Determines the element to focus when the popover is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.
Popover.Portal
Inherited from PopoverPortalProps
Whether to keep the portal mounted in the DOM while the popup is hidden.
Defaults to false.
Inherited from Props
A parent element to render the portal element into.
Popover.Positioner
Inherited from UseAnchorPositioningSharedParameters
An element to position the popup against. By default, the popup will be positioned against the trigger.
Determines which CSS `position` property to use.
Defaults to 'absolute'.
Which side of the anchor element to align the popup against. May automatically change to avoid collisions.
Defaults to 'bottom'.
Distance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. @example ```jsx <Positioner sideOffset={({ side, align, anchor, positioner }) => { return side === 'top' || side === 'bottom' ? anchor.height : anchor.width; }} /> ```
Defaults to 8.
How to align the popup relative to the specified side.
Defaults to 'center'.
Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. @example ```jsx <Positioner alignOffset={({ side, align, anchor, positioner }) => { return side === 'top' || side === 'bottom' ? anchor.width : anchor.height; }} /> ```
Defaults to 0.
An element or a rectangle that delimits the area that the popup is confined to.
Defaults to 'clipping-ancestors'.
Additional space to maintain from the edge of the collision boundary.
Defaults to 5.
Whether to maintain the popup in the viewport after the anchor element was scrolled out of view.
Defaults to false.
Minimum distance to maintain between the arrow and the edges of the popup. Use it to prevent the arrow element from hanging out of the rounded corners of a popup.
Defaults to 5.
Whether to disable the popup from tracking any layout shift of its positioning anchor.
Defaults to false.
Determines how to handle collisions when positioning the popup. `side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`): - `'flip'`: keep the requested side when it fits; otherwise try the opposite side (`top` and `bottom`, or `left` and `right`). - `'shift'`: never change side; keep the requested side and move the popup within the clipping boundary so it stays visible. - `'none'`: do not correct side-axis overflow. `align` controls overflow on the alignment axis (`start`/`center`/`end`): - `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows. - `'shift'`: keep side and requested alignment, then nudge the popup along the alignment axis to fit. - `'none'`: do not correct alignment-axis overflow. `fallbackAxisSide` controls fallback behavior on the perpendicular axis when the preferred axis cannot fit: - `'start'`: allow perpendicular fallback and try the logical start side first (`top` before `bottom`, or `left` before `right` in LTR). - `'end'`: allow perpendicular fallback and try the logical end side first (`bottom` before `top`, or `right` before `left` in LTR). - `'none'`: do not fallback to the perpendicular axis. When `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`. If `align` is omitted, it defaults to `'flip'`. @example ```jsx <Positioner collisionAvoidance={{ side: 'shift', align: 'shift', fallbackAxisSide: 'none', }} /> ```
Popover.Title
No additional props — forwards all standard HTML attributes to the underlying element.
Popover.Trigger
Inherited from NativeButtonProps
Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (e.g. `<div>`).
Defaults to true.