HTML Popover API Reference
Complete reference for the HTML Popover API — popover attribute, popovertarget, toggle events, ::backdrop, and CSS anchor positioning
You need a dropdown menu that closes when clicking outside, renders above all other content (including elements with z-index: 9999), and doesn’t need a single line of JavaScript for the open/close logic. Before the Popover API, that meant a click handler, a document-level listener for “click outside,” manual z-index management, and focus trapping. Now it’s popover="auto" on the content and popovertarget on the button — but the API is new enough that the docs are scattered.
Why This Reference (Not the HTML Dialog Element Guide)
PureDevTools has an HTML Dialog Element Guide for modal dialogs that block page interaction and trap focus. The Popover API is for non-blocking overlays — tooltips, dropdown menus, notification toasts, and popovers that dismiss when you click elsewhere. Both use the top layer, but popovers don’t trap focus or require showModal(). This reference covers auto vs manual mode, popovertarget, toggle events, ::backdrop, :popover-open, and CSS anchor positioning.
HTML Popover API Reference
The Popover API is a browser-native mechanism for displaying overlay content — tooltips, menus, modals-lite, and notifications — without JavaScript boilerplate. It provides a top-layer rendering context, light-dismiss behavior, and declarative HTML wiring through the popover attribute.
<!-- Minimal auto popover — no JavaScript needed -->
<button popovertarget="welcome">Show welcome message</button>
<div id="welcome" popover>
<p>Welcome! Click outside or press Escape to dismiss.</p>
</div>
popover=“auto” vs popover=“manual”
The popover attribute accepts two values that control dismissal behavior.
popover=“auto” (default)
popover and popover="auto" are equivalent. Auto popovers:
- Are rendered in the top layer — above all page content, including
position: fixedelements - Close automatically (light dismiss) when the user clicks outside, presses Escape, or another auto popover opens
- Only one auto popover can be open per stack level at a time
<button popovertarget="info">Open info</button>
<div id="info" popover>
<p>I close automatically on outside click or Escape.</p>
</div>
popover=“manual”
Manual popovers stay open until explicitly closed via JavaScript or a popovertargetaction="hide" button:
- Does not light-dismiss on outside click or Escape
- Multiple manual popovers can be open simultaneously
- Useful for notifications, persistent panels, and custom overlay stacks
<button popovertarget="toast" popovertargetaction="show">Show toast</button>
<div id="toast" popover="manual" role="status" aria-live="polite">
Changes saved successfully.
<button popovertarget="toast" popovertargetaction="hide">Dismiss</button>
</div>
Declarative Wiring: popovertarget and popovertargetaction
The popovertarget attribute connects a button to a popover by ID — no JavaScript required:
<!-- Opens/closes by default (toggle) -->
<button popovertarget="panel">Toggle panel</button>
<!-- Dedicated open button -->
<button popovertarget="panel" popovertargetaction="show">Open</button>
<!-- Dedicated close button inside the popover -->
<button popovertarget="panel" popovertargetaction="hide">Close ✕</button>
<div id="panel" popover>
<p>Panel content</p>
<button popovertarget="panel" popovertargetaction="hide">Close ✕</button>
</div>
popovertargetaction accepts three values:
"toggle"(default) — flip between open and closed"show"— only open the popover (idempotent)"hide"— only close the popover (idempotent)
JavaScript API
For programmatic control, three methods are available on any element with a popover attribute:
const popover = document.getElementById('my-popover');
popover.showPopover(); // Open — throws InvalidStateError if already open
popover.hidePopover(); // Close — throws InvalidStateError if already closed
// togglePopover is safe — returns boolean, does not throw
const isNowOpen = popover.togglePopover(); // flip state
popover.togglePopover(true); // force open
popover.togglePopover(false); // force close
togglePopover() returns true if the popover is now open, false if now closed.
toggle and beforetoggle Events
Both events are ToggleEvent instances with oldState and newState string properties ('open' or 'closed'):
const popover = document.getElementById('my-popover');
// After the state change (not cancelable)
popover.addEventListener('toggle', (e) => {
console.log(`${e.oldState} → ${e.newState}`);
if (e.newState === 'open') loadData();
if (e.newState === 'closed') cleanup();
});
// Before the state change (cancelable)
popover.addEventListener('beforetoggle', (e) => {
if (e.newState === 'closed' && hasUnsavedChanges) {
e.preventDefault(); // Keep popover open
}
});
::backdrop for Popovers
Popovers in the top layer render a ::backdrop pseudo-element. Unlike <dialog>, the browser default backdrop is transparent:
/* Add a dim overlay behind a popover acting as a modal */
#modal-popover::backdrop {
background-color: rgb(0 0 0 / 40%);
}
/* Frosted glass effect */
#frosted-popover::backdrop {
background-color: rgb(0 0 0 / 20%);
backdrop-filter: blur(6px);
}
:popover-open and CSS Animations
Use :popover-open to style the popover when it is visible, and @starting-style for enter animations:
[popover] {
opacity: 0;
transform: scale(0.95) translateY(-4px);
transition:
opacity 0.15s ease,
transform 0.15s ease,
overlay 0.15s allow-discrete,
display 0.15s allow-discrete;
}
[popover]:popover-open {
opacity: 1;
transform: scale(1) translateY(0);
}
/* Starting state for the enter animation */
@starting-style {
[popover]:popover-open {
opacity: 0;
transform: scale(0.95) translateY(-4px);
}
}
overlay and display in the transition list are required for exit animations to play before the element is hidden from the top layer.
CSS Anchor Positioning
CSS anchor positioning (Baseline 2024) lets you tether a popover to its trigger element:
/* Step 1: Register the trigger as an anchor */
button#menu-trigger {
anchor-name: --menu-anchor;
}
/* Step 2: Attach the popover to the anchor */
#menu-popover {
position: fixed;
position-anchor: --menu-anchor;
position-area: bottom span-all;
width: anchor-size(width); /* match trigger width */
margin-top: 4px;
}
position-area uses a 3×3 grid of keywords around the anchor:
- Physical:
top,bottom,left,right,center - Logical:
block-start,block-end,inline-start,inline-end,span-all
Accessibility Requirements
The popover attribute does not assign an implicit ARIA role. Always add an appropriate role:
<!-- Complex interactive content: role="dialog" -->
<button popovertarget="settings" aria-haspopup="dialog">Settings</button>
<div id="settings" popover role="dialog" aria-labelledby="settings-h">
<h2 id="settings-h">Settings</h2>
<input autofocus type="text" aria-label="Display name" />
</div>
<!-- Read-only hint: role="tooltip" -->
<button aria-describedby="tip">Help ℹ</button>
<div id="tip" popover role="tooltip">
Use ISO 8601 date format: YYYY-MM-DD
</div>
<!-- Action menu: role="menu" -->
<button popovertarget="menu" aria-haspopup="menu">Actions ▾</button>
<div id="menu" popover role="menu">
<button role="menuitem">Edit</button>
<button role="menuitem">Delete</button>
</div>
Focus management
- For interactive popovers (forms, menus): add
autofocuson the first interactive element - For non-interactive popovers (tooltips): do not move focus
- Return focus to the trigger element when the popover closes
Browser Support
The Popover API is Baseline 2024 — supported in all modern browsers:
| Browser | Version |
|---|---|
| Chrome | 114+ |
| Firefox | 125+ |
| Safari | 17+ |
| Edge | 114+ |
CSS anchor positioning requires Chrome 125+, Firefox 133+, Safari 18+.
Common Mistakes
Using popover="true" or popover="false": These are not valid values. popover="true" and popover="false" are both treated as popover="manual" (any value other than “auto” maps to manual). Use popover or popover="auto" for auto mode.
popovertarget value not matching the id: The match is exact and case-sensitive. A mismatch silently fails — the button does nothing.
Manual popover with no close path: Without a close button or JavaScript hidePopover() call, keyboard users cannot dismiss the popover, violating WCAG 2.1.2 (No Keyboard Trap).
Missing ARIA role: Unlike <dialog>, the popover attribute does not assign a role. Add role="dialog", role="tooltip", or role="menu" as appropriate.
FAQ
What is the difference between popover=“auto” and popover=“manual”?
popover="auto" (or just popover) creates a popover that closes automatically when the user clicks outside, presses Escape, or another auto popover opens. popover="manual" creates a popover that stays open until explicitly dismissed. Use auto for light-dismiss overlays and manual for persistent panels and notifications.
How is the Popover API different from the dialog element?
The <dialog> element with showModal() creates a true modal: it traps keyboard focus inside the dialog, makes all outside content inert, and shows a backdrop. The Popover API does not trap focus (except with autofocus) and does not make outside content inert. Use <dialog> when you need a blocking modal; use popover for non-blocking overlays, menus, and tooltips.
How do I position a popover relative to its trigger button?
Use CSS anchor positioning: set anchor-name on the trigger and position-anchor + position-area on the popover. Anchor positioning is Baseline 2024 (Chrome 125+, Firefox 133+, Safari 18+). For broader support, use absolute positioning with a wrapper element or a JavaScript positioning library.
Can I prevent a popover from closing when the user clicks outside?
Yes — listen for the beforetoggle event. When event.newState === 'closed' and you want to prevent dismissal, call event.preventDefault(). Note that blocking light-dismiss can harm usability; only do this when there are unsaved changes and always provide an alternative close path.
How do I animate a popover opening and closing?
Use CSS transitions on the [popover] element with display allow-discrete and overlay allow-discrete, and @starting-style to define the initial open state. This works in Chrome 117+, Firefox 129+, Safari 17.5+. The overlay property keeps the element in the top layer during the exit animation.
Does the Popover API work without JavaScript?
Yes — for basic open/close behavior. The popovertarget and popovertargetaction attributes provide fully declarative wiring. JavaScript is only needed for programmatic control (showPopover, hidePopover, togglePopover), event handling (toggle, beforetoggle), or dynamic behavior like timed auto-dismiss.