Popover API
Use the native HTML popover attribute to build tooltips, dropdowns, and flyouts that live in the top layer without JavaScript.
Overview
Key Points
- •`popover` attribute (default: auto) creates a top-layer element with light-dismiss behavior
- •`popovertarget="id"` on a button wires it to a popover without any JavaScript
- •`popovertargetaction` values: "toggle" (default), "show", "hide"
- •`popover="manual"` requires JS to close no automatic light-dismiss or Escape handling
- •JS API: `el.showPopover()`, `el.hidePopover()`, `el.togglePopover()`
- •`::backdrop` and the `popover:open` CSS pseudo-class allow styling open state
- •The `beforetoggle` and `toggle` events fire when the popover changes state
Examples
Declarative tooltip-style popover with no JavaScript
<!-- Trigger button wired by ID -->
<button popovertarget="info-popover">Show Info</button>
<!-- The popover itself hidden until opened -->
<div id="info-popover" popover>
<p>This is additional information about the feature.</p>
<p>Click outside or press Escape to dismiss.</p>
</div>
<style>
/* Style the open state */
#info-popover:popover-open {
padding: 1rem;
border: none;
border-radius: 8px;
background: #1e293b;
color: white;
max-width: 300px;
}
/* Position it near the trigger */
#info-popover {
position: absolute;
inset-block-start: anchor(bottom);
margin: 8px;
}
</style>No JS needed. The browser handles open/close, light-dismiss, and top-layer placement automatically.
Manual popover controlled by JavaScript
<button id="menu-btn" aria-expanded="false" aria-controls="nav-menu">
Menu
</button>
<nav id="nav-menu" popover="manual" role="menu">
<a href="/home" role="menuitem">Home</a>
<a href="/about" role="menuitem">About</a>
<a href="/contact" role="menuitem">Contact</a>
</nav>
<script>
const btn = document.getElementById('menu-btn');
const menu = document.getElementById('nav-menu');
btn.addEventListener('click', () => {
menu.togglePopover();
btn.setAttribute('aria-expanded', menu.matches(':popover-open'));
});
menu.addEventListener('toggle', (event) => {
btn.setAttribute('aria-expanded', event.newState === 'open');
});
</script>popover="manual" gives full JS control. Update aria-expanded to keep screen reader state accurate.
Notification banner using popover
<div id="toast" popover="manual" role="status" aria-live="polite">
<span id="toast-msg"></span>
<button popovertarget="toast" popovertargetaction="hide">✕</button>
</div>
<style>
#toast {
position: fixed;
bottom: 1rem;
right: 1rem;
padding: 0.75rem 1rem;
background: #065f46;
color: white;
border-radius: 8px;
border: none;
}
</style>
<script>
function showToast(message, duration = 3000) {
const toast = document.getElementById('toast');
document.getElementById('toast-msg').textContent = message;
toast.showPopover();
setTimeout(() => toast.hidePopover(), duration);
}
// Usage
showToast('Profile saved successfully!');
</script>Common Mistakes
- Using `popover="manual"` when you want light-dismiss manual popovers require explicit JS to close
- Forgetting to update `aria-expanded` on the trigger button when controlling a manual popover with JS
- Using popover for critical interactions that need a focus trap use `<dialog>` with showModal() instead
Note
The Anchor Positioning CSS spec (companion to Popover) allows you to position a popover relative to its trigger element using `anchor()` functions in CSS no JavaScript position calculation needed.
Exercises
Write HTML to solve each exercise. Use Preview to see it render in a live iframe, then mark as done.
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4