Skip to content

ComponentsOverlays

Popover

Floating content anchored to a trigger, for small forms, details, pickers and notifications. Positions itself automatically, flips to stay on screen, moves focus in and restores it on close, with an optional arrow.

React AriaSource

Installation

pnpm dlx shadcn@latest add @desyne/popover

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import {
  Popover,
  PopoverDialog,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover";
tsx
<PopoverTrigger>
  <Button>Open</Button>
  <Popover>
    <PopoverDialog>
      <PopoverTitle>Title</PopoverTitle>
      {/* content */}
    </PopoverDialog>
  </Popover>
</PopoverTrigger>

PopoverTrigger connects the first child (any pressable, usually a Button) to the Popover after it. Popover is the positioned surface; PopoverDialog adds padding and dialog semantics so focus moves in and the content is announced.

Always wrap content in PopoverDialog

Popover on its own is just a positioned container. That's what Menu and Select build on, because they bring their own roles. For anything else, put a PopoverDialog inside and name it with a PopoverTitle or an aria-label.

When to use

  • Popover — small, interactive content tied to a specific control: a quick form, a share panel, a detail card, a notification list.
  • Tooltip — a short, non-interactive label shown on hover or focus.
  • Menu — a list of actions.
  • Dialog — a task that needs the user's full attention, or content too large for a popover.
  • Sheet — longer secondary content that slides in from an edge.

Anatomy

tsx
<PopoverTrigger>
  <Button />                {/* trigger */}
  <Popover>                 {/* positioned surface, optional arrow */}
    <PopoverDialog>         {/* role="dialog", padding, focus management */}
      <PopoverTitle />      {/* accessible name */}
    </PopoverDialog>
  </Popover>
</PopoverTrigger>
PartRendersNotes
PopoverTrigger—Manages open state and wires the trigger to the popover. Same component as DialogTrigger.
Popover<div> (portal)Positioned surface with border, shadow and enter/exit animations. Renders an OverlayArrow when showArrow is set.
PopoverDialog<div role="dialog">Content wrapper with w-72 p-4. Receives focus on open.
PopoverTitleHeading (slot="title", <h2>)Becomes the dialog's accessible name.

Examples

With arrow

showArrow adds an arrow that points at the trigger and rotates with the placement. The default offset grows from 4px to 10px to make room for it. Arrows help most when the trigger is small, like an info icon.

Monthly active users

Placement

placement sets the preferred side and alignment: top, bottom, left, right, start, end, plus combinations like bottom start or top end. The default is bottom. When there isn't room, the popover flips to the opposite side (disable with shouldFlip={false}).

Forms

PopoverDialog accepts a render function, ({ close }) => …. Wrap a React Aria Form in it to validate the fields and close the popover after a successful submit.

Controlled

Pass isOpen and onOpenChange to PopoverTrigger to own the open state, for example to close the popover after a choice or to open it from code.

Status

Custom anchor

Use Popover without a trigger by passing triggerRef, isOpen and onOpenChange. It then positions itself relative to any element, here a field, while a separate link opens it.

Rich content

Any pressable can be the trigger, like an inline name that opens a profile card with an Avatar, a Badge and actions. Popovers open on press, not hover, so the content stays reachable for keyboard and touch users.

Reviewed by two days ago and approved the migration plan for the ledger service.

Recipes

Share panel

A copyable link, a Select for access level and an AvatarGroup of collaborators. Nested overlays like the select work inside a popover; Esc closes the innermost one first.

Notifications

An icon button with an unread count opens a scrollable list with "Mark all read". The trigger's aria-label includes the count, since the badge is visual only. p-0 on PopoverDialog lets the header and list run edge to edge.

Feedback form

A small form with a TextareaField and an emoji ToggleButtonGroup. The submit button shows isPending while the request runs, then the popover closes and a toast confirms.

Accessibility

  • PopoverDialog renders role="dialog". Focus moves into the popover on open (to an element with autoFocus, otherwise the popover itself) and returns to the trigger on close.
  • Name the dialog with PopoverTitle or aria-label. Without either, React Aria falls back to labelling it by the trigger.
  • The trigger gets aria-expanded and aria-controls, so screen readers announce that it opens a dialog.
  • Popovers are modal for assistive tech: content outside is hidden from screen readers while open. isNonModal turns this off, but only use it for patterns designed for it.
  • Clicking outside or pressing Esc closes the popover.

Keyboard

KeyAction
Space / EnterOpens the popover from the trigger
Tab / Shift+TabMoves focus through the popover's content
EscCloses the popover and returns focus to the trigger

Styling

Data attributes

On the Popover:

AttributePresent when
data-placementAlways: the resolved side, "top", "bottom", "left" or "right" (after flipping)
data-triggerSet to the component that opened it, e.g. "DialogTrigger", "MenuTrigger" or "Select"
data-enteringOpening animation is running
data-exitingClosing animation is running
data-slot="popover"Always

PopoverDialog has data-slot="popover-dialog" and PopoverTitle has data-slot="popover-title".

CSS variables

VariableValue
--trigger-widthWidth of the trigger, e.g. min-w-(--trigger-width) to match it
--trigger-anchor-pointThe point on the trigger the popover is anchored to, useful as a transform-origin

Customizing

  • className on Popover styles the surface; className on PopoverDialog sets the width and padding (defaults w-72 p-4).
  • The slide direction of the enter animation follows data-placement, so the popover always grows away from its trigger.
  • popoverStyles is exported: the class string for the surface (border, background, shadow, animations). Reuse it for custom floating elements.
tsx
import { popoverStyles } from "@/components/ui/popover";

Render props

className and children on Popover accept a function of its state:

tsx
<Popover className={({ placement }) => (placement === "top" ? "mb-1" : "")}>
  …
</Popover>

API Reference

PopoverTrigger

Prop

Type

Popover

Prop

Type

Also accepts every prop of React Aria's Popover.

PopoverDialog

Prop

Type

Also accepts every prop of React Aria's Dialog.

PopoverTitle

Accepts every prop of React Aria's Heading, including level to change the heading element (<h2> inside a dialog).

  • Tooltip — a short label on hover or focus.
  • Menu — a list of actions in a popover.
  • Dialog — a centered modal for focused tasks.
  • Date Picker — a calendar in a popover.