Popover
Floating content anchored to a trigger element with click-outside dismissal.
import * as Popover from '@gentleduck/primitives/popover'import * as Popover from '@gentleduck/primitives/popover'Anatomy
<Popover.Root>
<Popover.Trigger />
<Popover.Anchor /> {/* Optional custom anchor point */}
<Popover.Portal>
<Popover.Content>
<Popover.Arrow /> {/* Optional */}
<Popover.Close /> {/* Optional */}
</Popover.Content>
</Popover.Portal>
</Popover.Root><Popover.Root>
<Popover.Trigger />
<Popover.Anchor /> {/* Optional custom anchor point */}
<Popover.Portal>
<Popover.Content>
<Popover.Arrow /> {/* Optional */}
<Popover.Close /> {/* Optional */}
</Popover.Content>
</Popover.Portal>
</Popover.Root>Example
import * as Popover from '@gentleduck/primitives/popover'
function UserMenu() {
return (
<Popover.Root>
<Popover.Trigger className="px-3 py-1 border rounded">
Settings
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className="bg-white shadow-lg rounded-lg p-4 w-64 border"
sideOffset={5}
>
<p className="font-medium mb-2">User settings</p>
<input placeholder="Display name" className="w-full border rounded px-2 py-1 mb-2" />
<Popover.Close className="text-sm text-blue-600">Done</Popover.Close>
<Popover.Arrow className="fill-white" />
</Popover.Content>
</Popover.Portal>
</Popover.Root>
)
}import * as Popover from '@gentleduck/primitives/popover'
function UserMenu() {
return (
<Popover.Root>
<Popover.Trigger className="px-3 py-1 border rounded">
Settings
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className="bg-white shadow-lg rounded-lg p-4 w-64 border"
sideOffset={5}
>
<p className="font-medium mb-2">User settings</p>
<input placeholder="Display name" className="w-full border rounded px-2 py-1 mb-2" />
<Popover.Close className="text-sm text-blue-600">Done</Popover.Close>
<Popover.Arrow className="fill-white" />
</Popover.Content>
</Popover.Portal>
</Popover.Root>
)
}API
Popover.Root
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Controlled open state |
defaultOpen | boolean | false | Initial open state |
onOpenChange | (open: boolean) => void | - | Called on state change |
modal | boolean | false | Enable modal behavior (focus trap, scroll lock) |
dir | 'ltr' | 'rtl' | - | Reading direction for keyboard navigation |
Popover.Trigger
Toggles the popover. Sets aria-expanded and aria-controls automatically.
Popover.Anchor
Optional custom anchor point. By default, content is positioned relative to the Trigger. Use Anchor to position relative to a different element.
Popover.Portal
Portals content to document.body.
Popover.Content
The floating content. Positioned by the Popper engine relative to the trigger.
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | Preferred side |
sideOffset | number | 0 | Distance from anchor in pixels |
align | 'start' | 'center' | 'end' | 'center' | Alignment along the side |
alignOffset | number | 0 | Alignment offset in pixels |
forceMount | true | - | Keep mounted always |
onOpenAutoFocus | (event) => void | - | Intercept auto-focus |
onCloseAutoFocus | (event) => void | - | Intercept focus restoration |
onPointerDownOutside | (event) => void | - | Called on outside click |
onFocusOutside | (event) => void | - | Called when focus moves outside |
onInteractOutside | (event) => void | - | Called for any outside interaction |
onEscapeKeyDown | (event) => void | - | Called on Escape press |
trapFocus | boolean | context.open | Override whether focus is trapped inside the content |
disableOutsidePointerEvents | boolean | context.open | Override whether pointer events outside are blocked |
lockScroll | boolean | context.open | Override whether body scroll is locked while open |
The trapFocus, disableOutsidePointerEvents, and lockScroll props default to the popover's open state. Override them when integrating with animation libraries like motion that need custom lifecycle control during exit animations.
Popover.Arrow
Optional visual arrow pointing toward the anchor.
Popover.Close
Button that closes the popover.
Keyboard interactions
| Key | Action |
|---|---|
| Space / Enter | Toggle popover (on Trigger) |
| Escape | Close popover |
| Tab | Move focus within content, then out (non-modal) or wrap (modal) |