Add the popover attribute to any element and point a button at it with popovertarget="id", and the browser handles opening, closing, the top layer and Escape for you with no JavaScript. In scripts, call showPopover(), hidePopover() or togglePopover(), and listen for beforetoggle and toggle events. popover="auto" closes when you click outside; manual does not.
| Value | Click outside or Escape closes it | Other popovers |
|---|---|---|
| popover or popover="auto" | Yes | Opening one closes other auto popovers, except its ancestors |
| popover="manual" | No, close it yourself | Any number can be open at once |
| popover="hint" | Yes | Closes other hint popovers that are not its ancestors; meant for tooltips |
Updated
Popovers used to mean a positioning library, a click-outside handler and a z-index fight. The Popover API moves all of that into HTML. The playground below creates a real popover in your browser so you can switch its type, drive it from code and watch the events fire in order.
Your browser runs a real popover here: switch its type, call its methods and read the event log.
| Name | Kind | What it does |
|---|---|---|
| popover | Global attribute | Makes the element a popover: auto, manual or hint |
| popovertarget | Attribute on button or input | Id of the popover the control opens and closes |
| popovertargetaction | Attribute on button or input | toggle (default), show or hide |
| showPopover() | Method | Adds the popover to the top layer |
| hidePopover() | Method | Removes it from the top layer and hides it |
| togglePopover() | Method | Shows it if hidden, hides it if shown |
| beforetoggle | Event | Fires before the state changes; has oldState and newState |
| toggle | Event | Fires after the state changes; has oldState and newState |
| :popover-open | CSS pseudo-class | Matches a popover while it is showing |
| ::backdrop | CSS pseudo-element | The layer behind a shown popover |
These frames run with scripts switched off, and the popovers still work, because the behaviour comes from HTML.
Click the button, then click elsewhere or press Escape. The menu closes itself.
<style>
[popover] { border: 1px solid #cbd5e1; border-radius: 10px; padding: 8px 12px; box-shadow: 0 8px 24px rgb(0 0 0 / .12) }
[popover]:popover-open { display: grid; gap: 4px }
</style>
<button popovertarget="menu">Open menu</button>
<div id="menu" popover>
<a href="#">Profile</a>
<a href="#">Settings</a>
<a href="#">Sign out</a>
</div>Clicking outside does nothing here. Only the hide button closes it.
<style>
#toast { margin: auto 16px 16px auto; background: #0f172a; color: #fff; border: 0; border-radius: 10px; padding: 10px 14px }
</style>
<button popovertarget="toast" popovertargetaction="show">Save</button>
<button popovertarget="toast" popovertargetaction="hide">Dismiss</button>
<div id="toast" popover="manual">Changes saved</div>::backdrop styles the layer under the popover. It is still not modal; the page behind can be clicked to close it.
<style>
#info::backdrop { background: rgb(15 23 42 / .4) }
#info { border: 0; border-radius: 12px; padding: 16px; max-width: 220px }
</style>
<button popovertarget="info">What is this?</button>
<div id="info" popover>A short explanation that closes when you click away.</div>Check support before calling the methods, then read newState in the toggle event.
const tip = document.querySelector("#tip");
const supported = HTMLElement.prototype.hasOwnProperty("popover");
if (supported) {
tip.addEventListener("beforetoggle", (e) => {
if (e.newState === "open") tip.textContent = "Loaded at " + new Date().toLocaleTimeString();
});
tip.addEventListener("toggle", (e) => console.log(e.oldState, "->", e.newState));
document.querySelector("#show").addEventListener("click", () => tip.showPopover());
document.querySelector("#hide").addEventListener("click", () => tip.hidePopover());
}
Most popovers need no script at all. The button and the popover are linked by id, and the browser wires up the toggling, light dismiss and the Escape key.
Reach for showPopover() and its siblings when the trigger is not a button, such as opening a hint after a failed form check or closing a toast on a timer.
Auto fits menus and pickers that should vanish when attention moves elsewhere. Manual fits notifications that stack and stay until dismissed. Hint is aimed at hover and focus tooltips.
Whatever the type, keep the content short and make sure every action inside it can also be reached by keyboard.
MDN marks the Popover API as Baseline newly available since January 2025, meaning it works in the latest versions of the major browsers. Some newer parts, such as the hint value, may have narrower support, so check the compatibility table before relying on them.
An auto popover light dismisses: clicking outside it or pressing Escape closes it, and opening it closes other auto popovers. A manual popover stays open until a button or script closes it, and several can be open together, which suits toasts.
Escape closes auto and hint popovers, the same ones that close on a click outside. A manual popover ignores both, so it needs its own close button or a hidePopover() call.
Listen for the toggle event on the popover element. Its newState is "open" or "closed". The beforetoggle event fires just before the change, which is the place to prepare content.
In the centre of the viewport, with fixed positioning, in the top layer. Move it with your own CSS, or with anchor positioning where the browser supports it.
Related: JavaScript accordion and tabs, JavaScript event listeners, CSS z-index and stacking, aria-label examples, CSS selectors cheat sheet.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.