Persona

Popover

Overlay · 6 props · Updated Sep 22, 2026

A popover is an elevated card anchored to something on the page, holding a short passage and a control or two. It takes focus, because there is something in it to use, and it leaves the rest of the page live, because it is not asking the reader to stop.

  • Trigger the element that opens the popover. Supply an anchor as well and the surface positions against that instead, while the trigger keeps the click.
  • Container (when open) the elevated card: shape, elevation and shadow. It takes focus, because a popover is a surface the reader can act in.
  • Heading (optional, when open) names what the surface is about, in one line.
  • Content (when open) the body: a short passage and at most one or two actions. Anything that scrolls belongs in a different container.
1 of 4 parts falls below the 4.5:1 WCAG AA threshold for normal text in the dark theme only (△).

Three components open a surface over the page, and what separates them is how much of the page they claim.

A Tooltip describes the thing you are pointing at. It is role="tooltip", it never takes focus, and nothing inside it can be operated — a control in a tooltip is unreachable from a keyboard, so the moment the content has a button in it, it is not a tooltip.

A popover is a surface the reader acts in. It takes focus and holds controls, and the page behind stays scrollable and clickable.

A Modal takes the page over. The background goes inert, and the reader answers before anything else can happen.

So the two questions are whether there is something to do, and whether the rest of the page must wait. Nothing to do makes it a tooltip; the page having to wait makes it a modal; a workflow with more than one step is always a modal, because a popover has no room and closes itself. For a list of actions rather than a passage of content, use a Dropdown Menu.

Open the popover from a click, never a hover

Section titled “Open the popover from a click, never a hover”

The trigger opens on click and only on click — the underlying Radix trigger binds no hover or focus handler. Keyboard readers get in because the trigger is a button and Enter fires its click; there is nothing to configure.

That is deliberate rather than missing. A surface that takes focus cannot be opened by hovering, because a pointer is the one input that has no way to hand focus over, and a keyboard is the one input that cannot hover. If the content is short enough that hovering feels right, it is a tooltip.

trigger and anchor can be two different elements: the trigger keeps the click, and the popover positions itself against the anchor instead. Use that when the thing being described is not the thing being clicked — an icon beside a row that should open a card aligned to the whole row.

portal defaults to true, so the surface renders at the end of the document rather than beside the trigger. Where it appears and where it lives are separate questions; turn the portal off when the popover has to inherit something from its place in the tree, and expect an ancestor with overflow: hidden to clip it when you do.

Radix’s root props reach the popover, including the open state

Section titled “Radix’s root props reach the popover, including the open state”

open, defaultOpen and onOpenChange are all accepted — they are Radix’s own root props, which is what the props table’s closing line means when it says the component takes everything on PopoverPrimitive.PopoverProps. They are not listed in the table above it, so they are easy to conclude are absent. They are not, and triggerProps is not where the open state lives; that prop only reaches the trigger button.

modal is the same kind of inherited prop, and it defaults to false. That default is the behavior described above: no focus trap, the page still scrolls, clicks outside land where they were aimed and close the popover on the way. Setting it to true gives you a modal without a modal’s overlay, which leaves the reader trapped in a surface that does not look like it holds them.

A popover closes on Esc and on a click outside, and that is usually enough. For an explicit exit, contentProps={{ showCloseButton: true }} draws an X — but only supply it alongside heading, because with a heading the X gets its own row in the header, and without one it is positioned absolutely over the top right of the content.

Its accessible name is fixed at “Close” and nothing can be forwarded to it, so it cannot be renamed or targeted in a test. That is logged upstream; until it changes, the close button is an affordance you can turn on and cannot adjust.

Name the popover, because nothing else does

Section titled “Name the popover, because nothing else does”

Measured on this page’s own specimen: the surface is role="dialog" with no aria-label, no aria-labelledby and no aria-describedby. heading renders a bold paragraph and is not wired to the dialog, so a popover with a heading is still announced as an unnamed dialog.

Pass the name yourself — contentProps={{ 'aria-label': 'Verification details' }} reaches the dialog element, because contentProps spreads onto it. The trigger needs nothing: it already carries aria-haspopup="dialog", aria-expanded and aria-controls, and focus moves into the surface on open and returns to the trigger on close.

Hold one short passage and at most two actions

Section titled “Hold one short passage and at most two actions”

The popover is gone the moment the reader clicks away, and nothing in it is recoverable. Anything they need to hold onto while doing something else belongs on the page, and anything they need to compare against something else belongs next to it.

contentProps will take a maxHeight and scroll inside it, which is the component admitting more content than it should be asked to hold. Reach for it for a list that is occasionally long, not as permission for a form: a tall popover covers the thing it is anchored to, which is the only context the reader has for reading it.

Capitalization, punctuation and the rules that apply to every label are on Writing.

NameType
trigger

ReactElement<unknown, string | JSXElementConstructor<any>>

Component to use as trigger. Radix UI trigger component props will be merged with this element.

triggerProps

PopoverTriggerProps

Props to apply to Popover.Trigger within

anchor

ReactElement<PopoverAnchorProps, string | JSXElementConstructor<any>>

An optional element to position the Popover.Content against. If this part is not used, the content will position alongside the trigger.

contentProps

PopoverCardProps

Props to apply to Popover.Card within

portal

boolean

Render content within a portal

portalProps

PopoverPortalProps

Props applied to Radix UI Portal component

Also accepts every prop of PopoverPrimitive.PopoverProps.

Was this page helpful?