The popover attribute adds native show, hide, top-layer, and dismissal behavior to an ordinary HTML element. It is useful for non-modal floating interfaces such as menus, teaching tips, and small information panels.
A popover with no JavaScript
Connect a button to a popover’s id with popovertarget.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Popover</title>
</head>
<body>
<button popovertarget="account-menu">Account</button>
<div id="account-menu" popover>
<p>Signed in as <strong>han@example.com</strong></p>
<nav aria-label="Account">
<a href="#profile">Profile</a>
<a href="#settings">Settings</a>
</nav>
</div>
</body>
</html>The button toggles the popover and exposes the connection to accessibility APIs. The browser puts the panel in the top layer, and an automatic popover closes when the user presses Escape, clicks outside it, or opens another automatic popover.
Explicit actions
popovertargetaction accepts toggle (the default), show, or hide.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Popover actions</title>
</head>
<body>
<button popovertarget="shortcuts" popovertargetaction="show">
Show shortcuts
</button>
<aside id="shortcuts" popover aria-labelledby="shortcuts-title">
<h2 id="shortcuts-title">Keyboard shortcuts</h2>
<p><kbd>⌘</kbd> + <kbd>K</kbd> opens search.</p>
<button popovertarget="shortcuts" popovertargetaction="hide">
Close shortcuts
</button>
</aside>
</body>
</html>Automatic and manual popovers
The empty popover attribute is equivalent to popover="auto".
<div popover>Light dismisses and closes other auto popovers.</div>
<div popover="auto">Same behavior, written explicitly.</div>
<div popover="manual">Only explicit controls or script dismiss this.</div>
Use manual only when the interface truly needs to remain open. If you remove light dismiss, provide an obvious close control.
The JavaScript API
Popovers can also be controlled or observed from JavaScript.
const popover = document.querySelector('[popover]');
popover.showPopover();
popover.hidePopover();
popover.togglePopover();
popover.addEventListener('toggle', (event) => {
console.log(event.oldState, event.newState);
});
As with <details>, script should respond to native state rather than duplicate it with unnecessary classes and ARIA attributes.
Styling a popover
The :popover-open pseudo-class targets its open state. ::backdrop is available when a visual layer behind the popover is useful, though popovers remain non-modal.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css" />
<title>Styled popover</title>
</head>
<body>
<button class="trigger" popovertarget="palette">Open command palette</button>
<div class="palette" id="palette" popover aria-labelledby="palette-title">
<h2 id="palette-title">Jump to…</h2>
<a href="#dashboard">Dashboard</a>
<a href="#projects">Projects</a>
<a href="#people">People</a>
</div>
</body>
</html>* { box-sizing: border-box; }
body {
background: hsl(227 30% 17%);
color: white;
display: grid;
font: 1rem/1.5 system-ui, sans-serif;
margin: 0;
min-block-size: 100dvh;
place-items: center;
}
.trigger {
background: hsl(47 100% 60%);
border: 0;
border-radius: .4rem;
cursor: pointer;
font: inherit;
font-weight: 700;
padding: .75rem 1rem;
}
.palette {
background: hsl(0 0% 100%);
border: 0;
border-radius: .75rem;
box-shadow: 0 1rem 3rem hsl(227 40% 5% / .45);
color: hsl(227 30% 17%);
inline-size: min(90vw, 24rem);
opacity: 0;
padding: 1rem;
transform: translateY(.75rem);
transition:
opacity 180ms ease,
transform 180ms ease,
display 180ms allow-discrete,
overlay 180ms allow-discrete;
}
.palette:popover-open {
opacity: 1;
transform: translateY(0);
}
@starting-style {
.palette:popover-open {
opacity: 0;
transform: translateY(.75rem);
}
}
.palette a {
color: inherit;
display: block;
padding: .65rem;
}
:focus-visible {
outline: 3px solid hsl(190 90% 45%);
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.palette { transition: none; }
}Invoker Commands API
popovertarget is one example of declarative invocation: a button controls another element without a custom click listener.
The newer Invoker Commands API generalizes that idea with commandfor and command. For example, supporting browsers can open and close a dialog declaratively:
<button commandfor="help" command="show-modal">Open help</button>
<dialog id="help">
<h2>Help</h2>
<p>Choose a topic to continue.</p>
<button commandfor="help" command="close">Close</button>
</dialog>
This does not make JavaScript obsolete. It removes glue code so JavaScript can concentrate on application-specific behavior.
Check support before shipping
Popovers are broadly available, but newer values, positioning features, animation techniques, and invoker commands may have a different support baseline. Treat them as progressive enhancements unless your browser matrix confirms support.
[ A11y Checklist ]
- Choose semantic content inside the popover: use
<nav>for navigation, a list for options, and headings where useful. - Give icon-only triggers an accessible name.
- Do not add modal semantics to a popover; background content remains interactive.
- Keep focus styles visible and provide a close control for manual popovers.
- Use
<dialog>instead when the user must respond before continuing. - Use
<details>instead when content should expand in the document flow. - Test keyboard order carefully. Top-layer placement does not automatically make every composite widget behave like a menu or listbox.