The Popover API
For years, overlays required hefty JavaScript libraries — managing z-index, focus trapping, outside-click detection, Escape key handling, accessibility roles. The modern web ships all of this natively.
The Popover API and <dialog> element together give you a complete overlay system. The browser handles the top-layer promotion, focus management, light-dismiss, and backdrop — leaving you to write only the CSS you actually care about.
popover attribute
Add popover to any element. The browser promotes it to the top layer — above all other content, including fixed elements and stacking contexts.
popovertarget
Wire a button to any popover by ID. The browser handles the click, the toggle, the Escape key, and the light-dismiss — no addEventListener required.
<dialog> element
A semantic, accessible modal container. Provides focus trapping, a ::backdrop layer, and integrates with the top-layer stack beside popovers.
@starting-style
Define the before-open state of an element. The browser interpolates from it on entry — enabling pure-CSS show/hide transitions that were previously impossible.
<!-- The trigger button -->
<button popovertarget="my-popover">
Show Popover
</button>
<!-- The popover itself (auto = light-dismiss + Escape) -->
<div popover="auto" id="my-popover">
<p>I live in the top layer.</p>
</div>
<!-- manual = stays open until explicitly closed -->
<div popover="manual" id="sticky-tip">
I won't auto-dismiss.
</div>
<!-- closedby="any" allows backdrop-click to close -->
<dialog id="modal" closedby="any">
<h2>Confirm Action</h2>
<p>Are you sure?</p>
<form method="dialog">
<button value="confirm">Confirm</button>
<button value="cancel">Cancel</button>
</form>
</dialog>
<!-- Open it with JS -->
document.querySelector('#modal').showModal();
<!-- Or non-modal (no backdrop, no focus trap) -->
document.querySelector('#modal').show();
/* The final open state */
[popover]:popover-open {
opacity: 1;
transform: translateY(0);
transition:
opacity 400ms ease,
transform 400ms ease,
display 400ms allow-discrete,
overlay 400ms allow-discrete;
}
/* Where the animation begins — the "before open" state */
@starting-style {
[popover]:popover-open {
opacity: 0;
transform: translateY(1rem);
}
}
/* Exit: when NOT :popover-open (via allow-discrete) */
[popover]:not(:popover-open) {
opacity: 0;
transform: translateY(0.5rem);
}
popovertargetaction values
toggle
Default. Show if hidden, hide if visible.
show
Only show the popover. Idempotent if already open.
hide
Only hide the popover. Safe to call when closed.
closedby attribute
any
Close on Escape, close request, or clicking outside.
closerequest
Close only on Escape or platform close gesture.
none
Never auto-close. Requires explicit JS or button.