The Native Popover API
I counted 14KB of JavaScript in a dropdown component. Click-outside detection, z-index stacking, scroll/resize repositioning, Escape handling, focus management — all recreated by hand for a menu with four items. The Popover API does this in HTML:
<button popovertarget="menu">Options</button>
<div id="menu" popover>
<button>Edit</button>
<button>Delete</button>
</div>
Two elements. Zero JavaScript. Top-layer rendering, light dismiss, Escape to close. For non-modal overlays — tooltips, menus, pickovers, info panels — the platform finally has a native answer.
Basic popover
<button popovertarget="info">More info</button>
<div id="info" popover>
<p>Additional details about this feature.</p>
</div>
popovertargeton the button references the popover'sid- Clicking the button toggles the popover
- Clicking outside (light dismiss) closes it
- Escape closes it
- Renders in the top layer (above modals, sticky headers, everything)
Popover types
<!-- Auto popover (default): light dismiss, one at a time -->
<div popover="auto" id="menu">...</div>
<!-- Manual popover: no light dismiss, multiple can be open -->
<div popover="manual" id="tooltip">...</div>
| Type | Light dismiss | Escape closes | Multiple open |
|---|---|---|---|
auto |
Yes | Yes | No (closes others) |
manual |
No | Yes | Yes |
Use auto for menus and pickers. Use manual for tooltips that should stay open until explicitly closed.
JavaScript control
const popover = document.getElementById('menu');
popover.showPopover(); // open
popover.hidePopover(); // close
popover.togglePopover(); // toggle
// Events
popover.addEventListener('toggle', (e) => {
console.log('Popover state:', e.newState); // 'open' or 'closed'
});
popover.addEventListener('beforetoggle', (e) => {
if (shouldPrevent) e.preventDefault();
});
Styling popovers
[popover] {
padding: 0.5rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
background: white;
}
/* Entry animation */
[popover]:popover-open {
animation: pop-in 0.15s ease-out;
}
@keyframes pop-in {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
/* Backdrop (for popovers that need one) */
[popover]::backdrop {
background: transparent;
}
Popovers don't have a visible backdrop by default (unlike dialog). The background stays interactive.
Anchor positioning
Position popovers relative to their trigger:
#trigger {
anchor-name: --menu-trigger;
}
#menu {
position-anchor: --menu-trigger;
top: anchor(bottom);
left: anchor(left);
margin-top: 4px;
}
The popover positions below the trigger and repositions on scroll/resize automatically. No JavaScript positioning calculations.
Common patterns
Action menu
<button popovertarget="actions" aria-haspopup="true">
Actions ▾
</button>
<div id="actions" popover role="menu">
<button role="menuitem">Edit</button>
<button role="menuitem">Duplicate</button>
<button role="menuitem">Delete</button>
</div>
Tooltip (manual)
<span id="term" popovertarget="definition">HTTP/2</span>
<div id="definition" popover="manual" role="tooltip">
A binary protocol for multiplexed streams over a single connection.
</div>
Select picker
<button popovertarget="picker">
<span id="selected">Choose a color</span>
</button>
<div id="picker" popover>
<button onclick="select('red')">Red</button>
<button onclick="select('blue')">Blue</button>
<button onclick="select('green')">Green</button>
</div>
Popover vs dialog vs custom
| Feature | Popover | Dialog (showModal) | Custom JS |
|---|---|---|---|
| Top layer | Yes | Yes | Manual z-index |
| Light dismiss | Yes (auto) | Backdrop click | Manual |
| Focus trap | No | Yes | Manual |
| Inert background | No | Yes | Manual |
| Escape closes | Yes | Yes | Manual |
| Positioning | Anchor CSS | Centered | Manual |
| JS required | No | Minimal | Heavy |
Browser support
Popover API: Chrome 114+, Firefox 125+, Safari 17+, Edge 114+. Global coverage exceeds 90% in 2026. For older browsers, a small polyfill is available. Anchor Positioning has slightly lower support (Chrome 125+, Safari 18+) — provide a fallback position.
Popover + anchor positioning
The anchor attribute ties popover position to a trigger element. As the trigger scrolls, the popover follows. Combine with CSS @position-try fallbacks for viewport edge detection — flip the popover when it would overflow.
Form controls in popovers
Popover content can include fully interactive form elements. Unlike modal dialogs, focus isn't trapped — users can Tab out of the popover. For form popovers, consider manual focus management or switching to showModal() if the form requires completion before continuing.
Measuring success in production
Deploy changes behind feature flags when possible so you can compare metrics between control and treatment groups. Use Real User Monitoring to capture performance data from actual devices and network conditions — lab tools alone miss the long tail of user experiences. Set up alerts for regressions: a 10% LCP increase week-over-week warrants investigation before it hits CrUX.
Document your baseline metrics before making changes. Performance work without measurement is guesswork. Share results with the team — concrete numbers ("LCP improved 800ms on mobile") build support for continued investment in web performance and reliability.
Review changes quarterly. Browser updates, new API support, and traffic pattern shifts can obsolete previous optimizations or create new opportunities. What worked in 2024 may not be the best approach in 2026.
Additional production considerations
Teams often underestimate the maintenance cost of performance optimizations. Automate what you can: CI bundle budgets, Lighthouse CI on PRs, and RUM dashboards that alert on regressions. Manual audits don't scale past a handful of pages.
Security and performance intersect more than teams expect. Third-party scripts that hurt INP also expand your attack surface. Self-hosting fonts and critical assets reduces both latency and supply-chain risk. Review every external dependency quarterly — remove what you no longer need.
Accessibility and performance share goals: semantic HTML helps screen readers and gives the browser better rendering hints. Native elements like dialog, popover, and details reduce JavaScript while improving accessibility. Prefer platform features over custom implementations when they meet your requirements.
Mobile users dominate traffic for most sites. Test on real mid-tier Android hardware, not just desktop Chrome. Simulated throttling in DevTools approximates network conditions but not CPU constraints. A fix that helps desktop may be invisible on mobile if the bottleneck is JavaScript execution, not network.
Collaborate with backend teams on TTFB and API response times. Frontend optimizations can't fix a 2-second server response. Set SLAs for API endpoints that feed critical pages and measure them in the same RUM pipeline as Core Web Vitals.
Resources
- MDN Popover API
- HTML popover attribute spec
- CSS Anchor Positioning (MDN)
- Chrome Popover guide
- Can I use Popover API
Frequently asked questions
What does the native Popover API provide?
The Popover API adds a popover attribute to HTML elements that promotes them to the browser's top layer, above all other content without z-index management. It provides light dismiss (click outside to close), Escape to close, proper focus management, and declarative show/hide via popovertarget attributes. Before this API, every tooltip, dropdown, and popover required a JavaScript library managing overlays, focus traps, and click-outside detection.
How is the Popover API different from the dialog element?
Dialog with showModal() creates a modal with focus trapping, inert background, and a backdrop — designed for decisions that demand attention. Popover creates a non-modal overlay that does not trap focus or make the background inert — designed for contextual content like tooltips, menus, and pickers. Popovers support light dismiss by default. Use dialog for confirmations and forms; use popover for informational overlays and action menus.
Can I position popovers relative to a trigger element?
Yes, using CSS Anchor Positioning. The anchor-name property on the trigger and position-anchor on the popover connect them. The popover automatically positions relative to its anchor with anchor() offset functions. This replaces JavaScript positioning libraries that calculate coordinates on scroll and resize. Anchor Positioning is a separate spec but integrates naturally with the Popover API.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →