The HTML Popover API: Native Tooltips, Menus, and Toasts Without Libraries
Stop Importing Libraries for Popovers
Dropdown menus, tooltips, notification toasts — every app has them, and until now, every app needed JavaScript to build them. You’d wire up click handlers, manage visibility state, trap focus, listen for Escape key presses, and handle click-outside-to-close behavior. Or import a library that adds 10-30 KB to your bundle.
The HTML Popover API makes most of that unnecessary. It’s Baseline Widely Available — supported in Chrome 114+, Firefox 125+, Safari 17+, and Edge 114+ — and turns any element into a dismissible overlay with a single HTML attribute.
The Simplest Possible Popover
Two HTML attributes. That’s all you need.
<button popovertarget="my-popup">Open Menu</button>
<div id="my-popup" popover>
<p>This is a native popover. Click outside or press Escape to close.</p>
</div>No JavaScript. The button toggles the popover. It renders in the top layer (no z-index needed). Clicking outside closes it. Pressing Escape closes it.
The popover attribute accepts two values: auto (default) and manual. Auto popovers have light-dismiss. Manual popovers stay open until explicitly closed.
Popover Types: auto vs manual
auto (Default)
Light-dismiss is enabled. Only one auto popover can be open at a time — opening a new one closes the previous one. Perfect for dropdown menus and tooltips.
<button popovertarget="menu-1">Menu 1</button>
<div id="menu-1" popover>First menu</div>
<button popovertarget="menu-2">Menu 2</button>
<div id="menu-2" popover>Second menu</div>
<!-- Opening Menu 2 automatically closes Menu 1 -->manual
No light-dismiss. Multiple manual popovers can coexist. Use this for toast notifications, persistent helpers, or chat widgets.
<button popovertarget="toast" popovertargetaction="show">Show Toast</button>
<button popovertarget="toast" popovertargetaction="hide">Dismiss</button>
<div id="toast" popover="manual">
<p>Settings saved successfully</p>
</div>The popovertargetaction attribute takes toggle (default), show, and hide — letting you have separate open and close buttons.
Styling Popovers With CSS
Popovers are regular DOM elements. They get a ::backdrop pseudo-element when in the top layer, and a :popover-open pseudo-class when visible.
/* Backdrop overlay */
[popover]::backdrop {
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(2px);
}
/* Open state styling */
[popover]:popover-open {
border: 2px solid #4f46e5;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}Entry and Exit Animations
Use @starting-style to define the entry animation state:
[popover] {
opacity: 1;
transform: translateY(0);
transition: opacity 0.2s, transform 0.2s,
display 0.2s allow-discrete,
overlay 0.2s allow-discrete;
}
@starting-style {
[popover]:popover-open {
opacity: 0;
transform: translateY(-8px);
}
}
[popover]:not(:popover-open) {
opacity: 0;
transform: translateY(-8px);
}The allow-discrete keyword on display and overlay is essential — it lets the browser animate removal from the top layer instead of instantly hiding it.
JavaScript API
The JS API gives you programmatic control and event hooks:
const popup = document.querySelector('#my-popup');
popup.showPopover();
popup.hidePopover();
popup.togglePopover();
// toggle event fires AFTER state change
popup.addEventListener('toggle', (event) => {
console.log(event.newState); // 'open' or 'closed'
});
// beforetoggle fires BEFORE — great for lazy loading
popup.addEventListener('beforetoggle', async (event) => {
if (event.newState === 'open') {
popup.innerHTML = await fetchMenuItems();
}
});Building a Complete Dropdown Menu
A production-ready dropdown with zero JavaScript dependencies:
<nav>
<button popovertarget="file-menu" class="menu-trigger">File</button>
<div id="file-menu" popover class="dropdown">
<button onclick="handleNew()">New File</button>
<button onclick="handleOpen()">Open...</button>
<button onclick="handleSave()">Save</button>
<hr>
<button onclick="handleExit()">Exit</button>
</div>
</nav>.dropdown {
margin: 0; padding: 4px 0;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
min-width: 180px;
}
.dropdown button {
display: block; width: 100%;
padding: 8px 16px; border: none;
background: none; text-align: left;
cursor: pointer; font-size: 14px;
}
.dropdown button:hover { background: #f1f5f9; }Pair this with CSS Anchor Positioning to tether the dropdown to its trigger button, and you have a fully native, accessible dropdown menu.
Toast Notification System
Toasts are perfect for popover="manual" because they shouldn’t close on outside clicks.
function showToast(message, duration = 3000) {
const toast = document.getElementById('success-toast');
toast.textContent = message;
toast.showPopover();
setTimeout(() => toast.hidePopover(), duration);
}Popover vs Dialog
| Feature | Popover | <dialog> |
|---|---|---|
| Modal (blocks interaction) | No | Yes (showModal) |
| Light dismiss | Yes (auto) | No |
| Multiple open | Yes (manual) | One modal at a time |
| Focus trapping | No | Yes (modal) |
| Use case | Menus, tooltips | Confirmations, forms |
Common Mistakes
1. Using Popover for Modals
Popovers don’t trap focus. Screen reader users can tab past a popover into page content. For true modals, use <dialog> with showModal().
2. Forgetting popovertarget Must Match an ID
The popovertarget value must match the id exactly. Don’t include # — it’s a plain string match.
3. Missing Exit Animation Keywords
Without transition: display allow-discrete and transition: overlay allow-discrete, exit animations won’t work. The popover vanishes instantly.
Best Practices
- Use auto for single-instance overlays and manual for persistent ones.
- Pair with CSS Anchor Positioning for tethered popovers.
- Use beforetoggle for lazy loading — don’t fetch content on page load.
- Add ARIA attributes for complex widgets. The popover API handles basics, but comboboxes and similar widgets need
aria-expandedandrole. - Test keyboard navigation. Ensure open, interact, close all work with keyboard only.
Wrapping Up
The Popover API removes an entire category of JavaScript from your frontend. Menus, tooltips, toasts, and floating panels can be built with HTML attributes and CSS, with the browser handling dismissal, z-index, and focus management. Combined with Anchor Positioning and @starting-style, you get polished overlay UI that’s faster, lighter, and more accessible than any library.
FAQ
Can I use popover with any HTML element?
Yes. Any element can have the popover attribute — divs, sections, forms, anything. The element is hidden by default and rendered when toggled open.
Does popover replace title attributes for tooltips?
Not exactly. title shows unstyled browser tooltips on hover. Popover creates fully customizable overlays triggered by interaction. For rich, styled tooltips, use popover. For simple text hints, title still works.
Can I have nested popovers?
Yes, with caveats. Nested auto popovers must be in a parent-child DOM relationship. Otherwise, opening the child closes the parent. Manual popovers can always coexist.
How does this compare to Radix UI or Headless UI?
For simple popovers, the native API is a direct replacement. Radix and Headless UI add value for complex compound components (comboboxes, command palettes) that need extensive keyboard navigation and ARIA management beyond what popover provides natively.
editor's pick
latest video
news via inbox
Nulla turp dis cursus. Integer liberos euismod pretium faucibua

