RoadieRoadie
  • Home
  • Brand
  • Foundations
  • Content
  • Tokens
  • Components
  • Charts
  • Widgets
  • Appearance
HomeBrandFoundations
Appearance

Components

  • Navigation

    • Breadcrumb
    • LinkBeta
    • NavigatorAlpha
    • Steps
    • Tabs
  • Layout

    • Accordion
    • CollapsibleBeta
    • PaneAlpha
    • ScrollArea
    • Separator
  • Actions

    • Button
    • IconButton
    • ToggleBeta
    • Toggle groupBeta
    • ToolbarBeta
  • Fields

    • EditorAlpha
    • Field
    • Fieldset
    • Input
    • Label
    • Number fieldBeta
    • OTP fieldBeta
    • Query fieldAlpha
    • Textarea
  • Choices

    • Autocomplete
    • CheckboxBeta
    • Combobox
    • Radio group
    • Select
    • SliderBeta
    • SwitchBeta
  • Date and time

    • CalendarBeta
    • CalendarTile
    • Countdown
    • Dashboard periodBeta
    • Date fieldBeta
    • Date pickerBeta
    • Date range pickerBeta
    • DateTime
    • Duration
    • Time fieldBeta
  • Collections

    • Card
    • Carousel
    • DataTableBeta
    • ListAlpha
    • Marquee
    • RecordGridAlpha
    • RecordTableAlpha
    • SortableAlpha
    • TableBeta
  • Overlays

    • Dialog
    • Drawer
    • MenuBeta
    • Popover
    • ToastBeta
    • Tooltip
  • Status

    • Badge
    • CalloutBeta
    • DeltaBeta
    • Empty state
    • MeterBeta
    • ProgressBeta
    • SkeletonBeta
    • SparklineBeta
    • StatTileBeta
  • Media & brand

    • AvatarBeta
    • Icon Tile
    • Image
    • Logo
    • PatternBeta
    • QR codeBeta
    • SpotIllustration
  • Text

    • Code
    • Highlight
    • KbdBeta
    • Mark
    • Prose

Popover

Status
Stable
Added
Components 2.7.016 Jun 2026
Updated
Components 2.16.020 Oct 2026
Docs updated
19 Oct 2026

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.

<Popover>
<Popover.Trigger render={<Button>Open popover</Button>} />
<Popover.Content>
<Popover.Arrow />
<Popover.Header>
<Popover.Title>Notifications</Popover.Title>

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
  • accent to highlight something new
function IntentExamples() {
const [copied, setCopied] = useState(false)
function copyLink(open) {
setCopied(open)
if (open) {

Position

A popover opens below its trigger by default and re-flips to stay in view. Set these props on Popover.Content to control placement:

  • side (top/right/bottom/left)
  • align (start/center/end)
  • sideOffset, the gap from the trigger in px
  • alignOffset, the shift along the trigger's edge in px
<Popover>
<Popover.Trigger render={<Button>Filters</Button>} />
<Popover.Content side='bottom' align='start'>
<Popover.Arrow />
<Popover.Body>
<p className='text-sm'>Refine results by date, venue, and status.</p>

Hover

Set openOnHover on Popover.Trigger (with an optional delay) so the panel opens on hover as well as click. Use it for previews and inline help. It still opens on focus and click, so keyboard users aren't locked out.

<Popover>
<Popover.Trigger openOnHover delay={150} render={<Button>@jordan</Button>} />
<Popover.Content side='top'>
<Popover.Arrow />
<Popover.Header>
<Popover.Title>Jordan Lee</Popover.Title>

Composition

Popover is a compound component. Use Popover.Header, Popover.Body, and Popover.Footer to structure the panel, and Popover.Close to dismiss it.

<Popover>
<Popover.Trigger render={<Button>Account</Button>} />
<Popover.Content>
<Popover.Arrow />
<Popover.Header>
<Popover.Title>Signed in</Popover.Title>

With footer actions

A Popover.Footer holds a centered actions row. It suits a lightweight confirm-deletion flow anchored to the trigger.

<Popover>
<Popover.Trigger render={<Button intent='danger' emphasis='strong'>Delete item</Button>} />
<Popover.Content intent='danger'>
<Popover.Arrow />
<Popover.Header>
<Popover.Title>Delete item?</Popover.Title>

Guidelines

Use a popover for contextual content anchored to a control, such as menus, quick forms, and confirmations. It has no size prop and sizes to its content and anchor. 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. For destructive flows, naming the consequence, as in "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

Write the title as a clear question or noun phrase that names the affected object and the action. It 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. That covers 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. Put inline help to the right of a field, and a brief confirmation top of the button that fired it. 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, such as profile cards, definitions, and 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: Enter or Space on the trigger opens the popover; Escape closes it and returns focus to the trigger.
  • Default action: while the popover is open, Enter activates the strong primary action (the emphasis='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.Description are announced as the accessible name and description.
  • Icon-only close: When the dismiss control shows only an icon, give it an explicit label so screen readers announce its purpose. For example, <Popover.Close render={<IconButton aria-label='Close'><XIcon weight='bold' /></IconButton>} />.

API reference

Popover

defaultOpenboolean

Whether the popover is initially open. To render a controlled popover, use the `open` prop instead.

Defaults to false.

openboolean

Whether the popover is currently open.

onOpenChange((open: boolean, eventDetails: PopoverRootChangeEventDetails) => void)

Event handler called when the popover is opened or closed.

onOpenChangeComplete((open: boolean) => void)

Event handler called after any animations complete when the popover is opened or closed.

actionsRefRefObject<PopoverRootActions | null>

A ref to imperative actions. - `unmount`: Ends the closing phase of the popover after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the popover completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the popover imperatively when called.

modalboolean | "trap-focus"

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. On touch devices, a `true` modal blocks outside taps but leaves the page scrollable unless the popup spans nearly the full viewport width, matching native iOS behavior. 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.

triggerIdstring | null

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 (that is, when the `open` prop is not set).

defaultTriggerIdstring | null

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.

handlePopoverHandle<unknown>

A handle to associate the popover with a trigger. If specified, allows external triggers to control the popover's open state.

childrenReactNode | PayloadChildRenderFunction<unknown>

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. It forwards all standard HTML attributes to the underlying element.

Popover.Body

No additional props. It forwards all standard HTML attributes to the underlying element.

Popover.Close

classNamestring | ((state: PopoverCloseState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

Inherited from NativeButtonProps

nativeButtonboolean

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 (for example, `<div>`).

Defaults to true.

Popover.Content

classNamestring | ((state: PopoverPopupState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

intent"neutral" | "brand" | "brand-secondary" | "brand-blue" | "brand-orange" | "brand-purple" | "accent" | "danger" | "success" | "warning" | "info" | null
side"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end"

Defaults to 'bottom'.

align"center" | "start" | "end"

Defaults to 'center'.

sideOffsetnumber

Gap between trigger and popover, in px.

Defaults to 8.

alignOffsetnumber

Shift along the trigger's edge, in px.

Defaults to 0.

positionerProps(Omit<PopoverPositionerProps, PlacementKey> & { side?: Side; align?: Align; sideOffset?: number | ... 1 more ... | undefined; alignOffset?: number | ... 1 more ... | undefined; })

Props forwarded to the underlying Positioner.

Inherited from PopoverPopupProps

initialFocusboolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the popover is opened. By default, focus moves to the first tabbable element inside the popup, except when the popover is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard. - `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, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing.

finalFocusboolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)

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, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing.

Popover.Description

No additional props. It forwards all standard HTML attributes to the underlying element.

Popover.Footer

No additional props. It forwards all standard HTML attributes to the underlying element.

Popover.Header

No additional props. It forwards all standard HTML attributes to the underlying element.

Popover.Popup

classNamestring | ((state: PopoverPopupState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

intent"neutral" | "brand" | "brand-secondary" | "brand-blue" | "brand-orange" | "brand-purple" | "accent" | "danger" | "success" | "warning" | "info" | null

Inherited from PopoverPopupProps

initialFocusboolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the popover is opened. By default, focus moves to the first tabbable element inside the popup, except when the popover is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard. - `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, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing.

finalFocusboolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)

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, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing.

Popover.Portal

classNamestring | ((state: PopoverPortalState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

Inherited from PopoverPortalProps

keepMountedboolean

Whether to keep the portal mounted in the DOM while the popup is hidden.

Defaults to false.

containerHTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null

A parent element to render the portal element into.

Popover.Positioner

classNamestring | ((state: PopoverPositionerState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

Inherited from UseAnchorPositioningSharedParameters

anchorElement | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null

An element to position the popup against. By default, the popup will be positioned against the trigger.

positionMethod"absolute" | "fixed"

Determines which CSS `position` property to use.

Defaults to 'absolute'.

side"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end"

Which side of the anchor element to align the popup against. May automatically change to avoid collisions.

Defaults to 'bottom'.

sideOffsetnumber | OffsetFunction

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.

align"center" | "start" | "end"

How to align the popup relative to the specified side.

Defaults to 'center'.

alignOffsetnumber | OffsetFunction

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.

collisionBoundaryBoundary

An element or a rectangle that delimits the area that the popup is confined to.

Defaults to 'clipping-ancestors'.

collisionPaddingPadding

Additional space to maintain from the edge of the collision boundary.

Defaults to 5.

stickyboolean

Whether to maintain the popup in the viewport after the anchor element was scrolled out of view.

Defaults to false.

arrowPaddingnumber

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.

disableAnchorTrackingboolean

Whether to disable the popup from tracking any layout shift of its positioning anchor.

Defaults to false.

collisionAvoidanceCollisionAvoidance

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. It forwards all standard HTML attributes to the underlying element.

Popover.Trigger

classNamestring | ((state: PopoverTriggerState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

Inherited from NativeButtonProps

nativeButtonboolean

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 (for example, `<div>`).

Defaults to true.

Previous page← MenuNext pageToast →