The card below is a native popover โ wired with just
<button popovertarget> + popover="auto". No JavaScript for
open/close, click-outside dismiss, Escape, or z-index. The log shows the browser's own
toggle events. Everything you touch here is the real feature.
Click Open settings. Then close it however you like: click the button again, click anywhere outside, or press Esc. Watch the log in panel 3 name each event.
Settings
A plain <div popover> in the top layer โ above every stacking context.
Reset to defaults?
This popover opened from inside the other one โ both stack in the top layer, and there isn't a single z-index in this demo.
auto: click outside or press Esc to dismiss, and only one auto popover is open at a time.
Off = the popover snaps in instantly. On = it fades and slides โ the whole animation is one @starting-style block, no keyframes. Reopen the popover after toggling to see the difference.
Each line is a native toggle event โ the same event you'd listen to in real code. You wrote none of the dismiss logic.
// state + a click-outside listener
const [open, setOpen] = useState(false);
useEffect(() => {
const h = (e) => {
if (!ref.current?.contains(e.target))
setOpen(false);
};
document.addEventListener('mousedown', h);
return () => document
.removeEventListener('mousedown', h);
}, []);
/* + Escape handler + aria-expanded
+ position:absolute; z-index:9999 */
<button popovertarget="menu">
Options
</button>
<div id="menu" popover>
โฆ
</div>
// open/close, click-outside,
// Escape, top layer, aria-expanded
// โ all free. Zero JavaScript.
Baseline 2024 (Chrome 114, Firefox 125, Safari 17). What the API doesn't do: viewport-aware positioning โ this card just uses the browser's default centering. For dropdowns that flip and shift you still reach for Floating UI or CSS Anchor Positioning.