Dropdown menus, toasts, and tooltips have historically each required their own bespoke JS logic for top-layer rendering, dismissal, and positioning. The Popover API unifies all three into two HTML attributes and a handful of CSS hooks.
1popover: Any Element Becomes A Top-Layer Overlay
The popover attribute (shorthand for popover="auto") can be added to essentially any element ā a <div>, a <ul>, a <form> ā hiding it by default via a browser-applied display: none equivalent, and rendering it on the top layer, the same stacking context <dialog> uses, once shown. This means a popover always renders above regular document content regardless of z-index, without the developer managing a portal or manual stacking context.
Unlike <dialog>, a popover never draws a backdrop and never makes the rest of the page inert ā it's designed for overlays that coexist with an interactive page underneath, exactly the behavior a dropdown menu or tooltip needs but a blocking modal doesn't.
2Declarative Triggers: popovertarget And popovertargetaction
popovertarget="menu" on a <button> links it to the popover with a matching id, and by default toggles it: showing it if hidden, hiding it if currently shown. popovertargetaction="show" or "hide" pins a button to one direction only, useful when "open" and "close" are two visually distinct controls (an open button plus an in-panel close button) rather than a single toggle.
This mechanism replaces what used to require addEventListener("click", ...) plus manual open/closed state tracking with pure HTML ā the browser owns the shown/hidden state, eliminating an entire category of state-desync bugs where JS and the DOM disagree about whether a menu is open.
3auto vs manual: Choosing The Right Dismissal Behavior
popover="auto" (the default) enables light-dismiss: clicking outside the popover or pressing Escape closes it automatically, and opening a new auto popover closes any other currently-open auto popover ā exactly the behavior expected of a dropdown menu, combobox listbox, or command palette. popover="manual" disables all of that: the popover only opens or closes via an explicit showPopover()/hidePopover()/togglePopover() call or a popovertargetaction-pinned button.
Choosing correctly matters for real UX: a toast notification using auto would vanish the instant a user clicked anywhere else on the page, defeating its purpose ā manual, paired with a setTimeout to auto-hide after a few seconds, is the correct mode for that pattern.
4Styling The Visible State And Positioning With Anchors
The :popover-open pseudo-class matches a popover only while it's actually shown, the correct hook for entrance/exit transitions ā combined with @starting-style and transition-behavior: allow-discrete the same way as an animating <dialog>, since a hidden popover is also fully removed from rendering via display: none.
CSS anchor positioning solves the previously JS-library-dependent problem of placing a popover relative to its trigger: anchor-name on the trigger element gives it a named anchor reference, and position-anchor plus anchor() functions on the popover position it relative to that anchor's edges ā automatically repositioning on scroll and resize with zero JavaScript, directly replacing what libraries like Popper.js were built to solve.
5Step-by-Step Breakdown
Menus, Toasts, And Tooltips ā Declared, Not Scripted. Dropdown menus, toast notifications, and tooltips all share the same underlying need: render above everything else, and dismiss on outside click or Escape. The Popover API gives every element that behavior via two HTML attributes, no JavaScript required.
popover Turns Any Element Into A Top-Layer Overlay. Adding the popover attribute (popover="auto" is the default when just popover is written) to any element hides it by default and, once shown, renders it on the same top layer <dialog> uses ā above the rest of the page, with automatic light-dismiss behavior.
The popover Attribute. What does adding a bare popover attribute to an element do?
- āHides it by default; when shown, it renders on the top layer with light-dismiss behavior
- āOnly applies default popover CSS styling, with no behavior change
- āImmediately shows the element; JS is required to hide it
popovertarget + popovertargetaction: Zero-JS Triggers. A <button popovertarget="menu"> declaratively wires itself as that popover's trigger, toggling it open/closed by default; popovertargetaction="show" or "hide" pins the button to only one direction, useful when open and close are separate controls.
Declarative Popover Triggers. What does <button popovertarget="menu"> do by default, with no popovertargetaction specified?
- āToggles the referenced popover open and closed on each click
- āOnly ever shows the popover, never hides it
- āNothing ā popovertargetaction is required for any behavior
Light Dismiss: Automatic For popover="auto", Manual With popover="manual". popover="auto" (the default) closes automatically on an outside click or Escape, and closes any other open auto popover when a new one opens ā the correct behavior for menus and comboboxes. popover="manual" opts out entirely, requiring explicit show/hide/toggle calls ā appropriate for a toast notification that shouldn't disappear just because the user clicked elsewhere.
Choosing auto vs manual. A toast notification should remain visible for a few seconds regardless of what the user clicks elsewhere on the page. Which popover mode fits?
- āpopover="auto"
- āpopover="manual"
- āNeither mode supports this; a custom overlay is required
:popover-open Styles The Visible State. The :popover-open pseudo-class matches a popover element while it's actually showing, the natural hook for entrance/exit CSS transitions ā paired with @starting-style the same way as a <dialog>, since a hidden popover is also removed from rendering entirely.
:popover-open Pseudo-Class. When does the :popover-open pseudo-class match a popover element?
- āWhile the popover is actually being shown
- āAlways, regardless of visibility
- āOnly while the mouse hovers over it
CSS Anchor Positioning Ties A Popover To Its Trigger. CSS anchor positioning lets a popover position itself relative to the element that triggered it ā anchor-name on the trigger, position-anchor plus anchor() functions on the popover ā solving tooltip/menu placement natively, without a JS positioning library recalculating on every scroll and resize.
CSS Anchor Positioning. What problem does CSS anchor positioning solve for popovers like menus and tooltips?
- āPositioning the popover relative to its trigger element, natively, without a JS library recalculating on scroll/resize
- āApplying color themes to popovers based on trigger state
- āIt's unrelated to popovers entirely
Popover API Mastered. You now know how to build menus, toasts, and tooltips using the popover attribute and popovertarget triggers, choose between auto and manual dismissal modes, animate them with :popover-open, and position them natively with CSS anchor positioning.
Build A Native Popover. The Popover API links a trigger button to a popover element via popovertarget.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Popovers Receive Reasonable Default Focus And Keyboard Behavior, But Verify Roles Explicitly
A generic popover doesn't imply a specific ARIA role (menu, listbox, tooltip) ā add the correct role and, where applicable, aria-expanded on the trigger for the specific widget pattern you're building.
2Escape-To-Close Is Automatic For auto Popovers, Matching User Expectation
This built-in behavior means keyboard users get correct dismissal for free with auto mode, one less interaction to hand-implement and test.
SEO Implications
- 1
Popover Content Is Present In The DOM And Crawlable Even While Hidden
Unlike some older show/hide patterns that inject content only on interaction, a popover element's content exists in the DOM from page load, so search engines can index it even though it's visually hidden by default.
Best Practices
Default To popover="auto" Unless You Have A Specific Reason For manual
Auto light-dismiss matches user expectation for the vast majority of overlay patterns (menus, comboboxes); manual is a deliberate opt-out for cases like toasts that must resist outside clicks.
Pair Every Popover Trigger With A Real Focusable Element (a <button>)
popovertarget is designed for interactive elements like <button>; using it on a non-interactive element loses built-in keyboard activation and accessible role semantics.
Frequent Bugs
A toast notification disappears the instant the user clicks anywhere else on the page.
Switch from the default popover="auto" to popover="manual" and control its visibility explicitly with showPopover()/hidePopover(), typically on a timer.
A dropdown menu doesn't reposition correctly when the page is scrolled or resized.
Use CSS anchor positioning (anchor-name on the trigger, position-anchor + anchor() on the popover) instead of one-time JS-computed coordinates, so positioning recalculates automatically.
Real-World Examples
A Fully Declarative Account Menu
A header account dropdown built entirely with popover, popovertarget, and anchor positioning ā no JavaScript at all.
<button popovertarget="account-menu" style="anchor-name:--account-btn">Account ā¾</button>
<div id="account-menu" popover style="position-anchor:--account-btn; top:anchor(bottom); left:anchor(left);">
<a href="/profile">Profile</a>
<a href="/logout">Log out</a>
</div>