Popover
Anchored floating panel with outside-click and Escape dismissal.
import { Popover, PopoverContent, PopoverTrigger } from '@elirobinson/react/components/organisms/Popover';Styles: @elirobinson/react/styles/organisms/Popover.css — already included when you import @elirobinson/react/styles.css.
Show code
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@elirobinson/react/components/organisms/Popover';
export default function Basic() {
return (
<Popover>
<PopoverTrigger className="ds-button ds-button--secondary">
What's included?
</PopoverTrigger>
<PopoverContent>
<p>Every coaching guide ships as a versioned PDF plus a printable practice-plan card.</p>
</PopoverContent>
</Popover>
);
}When to use it
Use Popover for content that's genuinely optional to see — extra detail, a small set of
filters, a short form — anchored to whatever triggered it. Nothing about it blocks the page: no
focus trap, no modal backdrop, background content stays fully interactive. If dismissing it
needs to be unmissable, or it needs to hold focus, that's a Dialog, not a Popover.
Controlled, with a manual dismiss
There's no PopoverClose — unlike Dialog, closing programmatically means calling
onOpenChange(false) yourself from inside the content.
Show code
import { useState } from 'react';
import { Button } from '@elirobinson/react/components/atoms/Button';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@elirobinson/react/components/organisms/Popover';
const sports = ['All sports', 'Soccer', 'Basketball', 'Track'];
export default function Controlled() {
const [open, setOpen] = useState(false);
const [sport, setSport] = useState('All sports');
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger className="ds-button ds-button--secondary">Filter: {sport}</PopoverTrigger>
<PopoverContent>
<div className="demo-col">
{sports.map((option) => (
<Button
key={option}
variant={option === sport ? 'primary' : 'ghost'}
onClick={() => setSport(option)}
>
{option}
</Button>
))}
<Button variant="secondary" onClick={() => setOpen(false)}>
Done
</Button>
</div>
</PopoverContent>
</Popover>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | false | — |
onOpenChange | ((open: boolean) => void) | — | — |
open | boolean | — | — |
PopoverContent
No props of its own beyond the inherited HTML attributes.
Also accepts all HTMLAttributes<HTMLDivElement> props.
PopoverTrigger
No props of its own beyond the inherited HTML attributes.
Also accepts all ButtonHTMLAttributes<HTMLButtonElement> props.
Accessibility
PopoverContentrendersrole="dialog", portaled todocument.body. This is a non-modal dialog role: nothing callsshowModal(), there's no focus trap, and the rest of the page stays interactive — the opposite ofDialog.Escapecloses it from anywhere (a document-level listener, the sameuseEscapeKeyhookDropdownMenuuses), and clicking outside the trigger or content closes it too.- No initial-focus management: opening the popover doesn't move DOM focus into
PopoverContent. Focus stays wherever it was — typically the trigger, since activating it doesn't blur it. If your content includes interactive elements a fully keyboard-driven flow depends on, move focus there yourself, or rely on the user tabbing in. - No built-in close affordance: there's no
PopoverClosesubcomponent. Closing it from inside the content means callingonOpenChange(false)yourself. - Content is positioned with fixed coordinates computed from the trigger's bounding rect,
the same anchoring mechanism
DropdownMenuuses — calculated once when it opens, not re-measured on scroll or resize.
Do
- Use Popover for supplementary, non-blocking content — a hint, a filter panel, a short form.
- Wire your own dismiss control inside PopoverContent when the content needs an explicit close action.
- Keep content lightweight — nothing here traps focus, so a long or complex form is a sign you actually want Dialog.
Don't
- Use it for anything that must block interaction with the rest of the page — reach for Dialog, an actual native modal.
- Assume focus moves into the content on open — it doesn't; test keyboard flows accordingly.
- Render PopoverContent or PopoverTrigger outside a Popover — it throws.