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 |
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) |