0Support

Popover API playground

Create non-modal top-layer content with the popover attribute. Compare declarative controls and JavaScript, test light dismiss, and copy reusable HTML, CSS, and JS.

Read the guide

Ask AI

Popover API playground

Response generated with openai/gpt-5.4-nano. AI can make mistakes. Always review the result.
HTML
<button type="button" popovertarget="feature-popover" popovertargetaction="toggle">
  Open popover
</button>

<div
  class="popover-card"
  id="feature-popover"
  popover
>
  <h2>Actions panel</h2>
  <p>This content lives in the top layer without creating a modal.</p>
  <button type="button" popovertarget="feature-popover" popovertargetaction="hide">
    Close
  </button>
</div>
CSS
.popover-card {
  max-inline-size: min(24rem, calc(100vw - 2rem));
  padding: 1.25rem;
  border: 1px solid light-dark(#b8c3ee, #496096);
  border-radius: 1rem;
  background: light-dark(#fffdf7, #10182c);
  color: light-dark(#17203a, #f4f7ff);
  box-shadow: 0 1.25rem 3rem rgb(8 15 35 / 0.28);
  opacity: 0;
  transform: translateY(0.5rem) scale(0.98);
  transition:
    opacity 180ms ease,
    transform 180ms ease,
    display 180ms allow-discrete,
    overlay 180ms allow-discrete;
}

.popover-card:popover-open {
  opacity: 1;
  transform: translateY(0) scale(1);
}

@starting-style {
  .popover-card:popover-open {
    opacity: 0;
    transform: translateY(0.5rem) scale(0.98);
  }
}

@media (prefers-reduced-motion: reduce) {
  .popover-card {
    transition: none;
  }
}

.popover-card::backdrop {
  background: rgb(8 15 35 / 0.28);
  backdrop-filter: blur(3px);
}
JS

Educational guide to the Popover API

Content above the page, without blocking it

Learn to connect buttons and panels with HTML, choose dismissal rules, and preserve an accessible interaction. Then adjust the behavior and code in the playground.

Back to the playground

Mental model

Popover is a presentation behavior. It is neither a new HTML element nor menu or modal semantics.

From the document to the top layerStatic diagram: it explains the relationship between button, document, and panel. The following examples provide the native test.
  1. 1 · InvokerThe button references the panel ID through popovertarget.
  2. 2 · DocumentThe content and button keep their place in the DOM; the rest of the page remains available.
  3. 3 · Top layerThe open panel is painted above ordinary content, outside its stacking contexts.

The browser manages the top layer. When opened, the panel leaves normal visual flow and is not clipped by its ancestors' overflow. You do not need to compete with z-index, and its DOM position does not change. Layer order also matters within the top layer.

A popover is non-modal: it does not make the rest of the document inert or trap focus. It can display help, actions, or supplemental content. If users must complete or cancel a task before continuing, choose a modal dialog.

Declarative invocation

An ID and two button attributes are enough to show and hide content without JavaScript listeners.

The popovertarget value is the ID, without #. popovertargetaction accepts toggle, show, or hide; omitting it defaults to toggle. Use type="button" so the control does not submit a form. An empty popover attribute means auto.

Supplemental help with light dismiss

Open the help, press Tab to enter it, and try Escape, the close button, or an outside click. In a browser without Popover, the content stays inline and the invocation buttons are hidden.
Supplemental help with light dismiss
<button id="popover-guide-auto-opener" type="button" class="popover-guide-auto-control" popovertarget="popover-guide-auto" popovertargetaction="toggle">
  Read copying tips
</button>
<aside id="popover-guide-auto" popover="auto" aria-labelledby="popover-guide-auto-heading">
  <h5 id="popover-guide-auto-heading">Before copying an example</h5>
  <p>Read the HTML and CSS together. Check the wording, font size, and keyboard behavior before incorporating the recipe into your interface.</p>
  <button type="button" class="popover-guide-auto-control" popovertarget="popover-guide-auto" popovertargetaction="hide">
    Close panel
  </button>
</aside>
:root {
  color-scheme: light dark;
}

.popover-guide-auto-control {
  display: none;
  min-block-size: 2.75rem;
  max-inline-size: 100%;
  padding: 0.5rem 1rem;
  border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  border-radius: 16px;
  corner-shape: squircle;
  background: light-dark(color-mix(in oklch, oklch(0.95 0.008 95) 98%, white), color-mix(in oklch, oklch(0.22 0.024 260) 86%, black));
  color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
  font: inherit;
  overflow-wrap: anywhere;
  cursor: pointer;

  &:focus-visible {
    outline: 3px dashed light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
    outline-offset: 3px;
  }
}

#popover-guide-auto {
  box-sizing: border-box;
  max-inline-size: 100%;
  padding: min(1rem, 4vi);
  border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  border-radius: 16px;
  corner-shape: squircle;
  background: light-dark(oklch(0.98 0.005 95), oklch(0.16 0.02 260));
  color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
  font-size: 1rem;
  line-height: 1.65;
  overflow-wrap: anywhere;

  > h5 {
    margin: 0;
    font-size: 1.25rem;
  }

  > p {
    margin-block: 1rem;
  }

  &::backdrop {
    background: transparent;
  }

  &:popover-open {
    box-shadow: light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260)) 0px 1px 4px;
  }
}

@supports selector(:popover-open) {
  .popover-guide-auto-control {
    display: inline-block;
  }

  #popover-guide-auto {
    position: fixed;
    inset: 0;
    margin: auto;
    inline-size: min(28rem, calc(100vi - 2rem));
    max-block-size: 70dvb;
    overflow: auto;
  }
}

The recipe applies positioning and reveals buttons inside @supports selector(:popover-open). When the browser ignores the attribute, the aside remains readable in normal flow. The fallback is inline content, without simulating light dismiss or the top layer. Opening and dismissal are native; only the manual reference adds JavaScript to restore focus.

commandfor and command can also invoke popovers using toggle-popover, show-popover, and hide-popover. They are another API with separate support. This recipe uses popovertarget to teach Popover's specific association without depending on those commands.

Modes and dismissal rules

Choose a mode based on content lifetime and the exit it needs to provide, rather than its appearance.

popover="auto"

Light dismiss and close requests, such as Escape. Opening another independent auto popover usually closes the previous one; nested popovers can stay open together. Use it for a panel that should disappear when the user continues outside.

popover="hint"

Help subordinate to auto. Showing a hint does not close the open auto popover; it replaces other independent hints and supports light dismiss and close requests. Check its availability separately.

popover="manual"

Explicit dismissal through a button or API. Outside clicks and Escape do not close it natively. Multiple manual popovers can coexist; each needs a clear exit and should not obscure essential content.

hint does not open on hover automatically. The mode defines coexistence and dismissal rules; invocation needs code or another supported API. Also, clicking an invoker outside an open auto popover can light-dismiss it even when the target is hint. Do not make necessary information available only on pointer hover.

A reference that stays open

Open the reference and try Escape or an outside click: it stays open. The explicit close control receives focus through autofocus. On dismissal, local JavaScript returns focus to the invoker; Tab can continue through the rest of the page.
A reference that stays open
<button id="popover-guide-manual-opener" type="button" class="popover-guide-manual-control" popovertarget="popover-guide-manual" popovertargetaction="show">
  Open persistent reference
</button>
<aside id="popover-guide-manual" popover="manual" aria-labelledby="popover-guide-manual-heading">
  <h5 id="popover-guide-manual-heading">Review checklist</h5>
  <p>Check labels, visible focus, an explicit exit, and reflow. This reference stays open until you use the close button; it does not prevent you from using the page.</p>
  <button type="button" class="popover-guide-manual-control" popovertarget="popover-guide-manual" popovertargetaction="hide" autofocus>
    Close panel
  </button>
</aside>
:root {
  color-scheme: light dark;
}

.popover-guide-manual-control {
  display: none;
  min-block-size: 2.75rem;
  max-inline-size: 100%;
  padding: 0.5rem 1rem;
  border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  border-radius: 16px;
  corner-shape: squircle;
  background: light-dark(color-mix(in oklch, oklch(0.95 0.008 95) 98%, white), color-mix(in oklch, oklch(0.22 0.024 260) 86%, black));
  color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
  font: inherit;
  overflow-wrap: anywhere;
  cursor: pointer;

  &:focus-visible {
    outline: 3px dashed light-dark(oklch(0.46 0.14 261), oklch(0.82 0.18 261));
    outline-offset: 3px;
  }
}

#popover-guide-manual {
  box-sizing: border-box;
  max-inline-size: 100%;
  padding: min(1rem, 4vi);
  border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  border-radius: 16px;
  corner-shape: squircle;
  background: light-dark(oklch(0.98 0.005 95), oklch(0.16 0.02 260));
  color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
  font-size: 1rem;
  line-height: 1.65;
  overflow-wrap: anywhere;

  > h5 {
    margin: 0;
    font-size: 1.25rem;
  }

  > p {
    margin-block: 1rem;
  }

  &::backdrop {
    background: transparent;
  }

  &:popover-open {
    box-shadow: light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260)) 0px 1px 4px;
  }
}

@supports selector(:popover-open) {
  .popover-guide-manual-control {
    display: inline-block;
  }

  #popover-guide-manual {
    position: fixed;
    inset: 0;
    margin: auto;
    inline-size: min(28rem, calc(100vi - 2rem));
    max-block-size: 70dvb;
    overflow: auto;
  }
}
(() => {
  const panel = document.getElementById('popover-guide-manual');
  const opener = document.getElementById('popover-guide-manual-opener');

  if (!panel || !opener || panel.dataset.focusBound) return;
  panel.dataset.focusBound = 'true';

  panel.addEventListener('toggle', (event) => {
    if (event.newState === 'closed') opener.focus();
  });
})();

showPopover(), hidePopover(), and togglePopover() let you coordinate a panel with application logic. beforetoggle precedes the change and toggle reports it through oldState/newState. Toggle events may be coalesced during rapid changes; do not treat them as a log of every call. The method alone does not provide the declarative button's implicit ARIA relationship.

JavaScript API reference

A snippet for the auto panel above: it defines operations to call from your logic and observes the final state. It is not executed in this guide and adds no listeners to the native buttons.

JavaScript API reference
JavaScript
const panel = document.getElementById('popover-guide-auto');

if (panel && 'popover' in HTMLElement.prototype) {
  const help = {
    show: () => panel.showPopover(),
    hide: () => panel.hidePopover(),
    toggle: () => panel.togglePopover(),
  };

  panel.addEventListener('toggle', (event) => {
    panel.dataset.state = event.newState;
  });

}

Focus and pattern choice

The platform handles part of the interaction; content structure and purpose remain the interface's responsibility.

With popovertarget, the browser sets an implicit aria-expanded/aria-details relationship and places open content after the invoker in Tab order. This is not a focus trap. In auto, pressing Escape from inside allows focus to return to the invoker; an outside click may focus the clicked destination. manual does not guarantee that restoration: the example adds a local toggle listener to return focus to the button on dismissal.

Without an autofocus target, opening a popover does not imply focusing the first control. The auto help leaves focus on the invoker; the manual reference deliberately moves it to the close control. Choose a useful initial focus and test content reading and return behavior, avoiding unnecessary jumps.

popover does not turn a div into a menu or tooltip. Here we use an aside with an associated heading for supplemental content and buttons with visible names. A role="menu" menu needs the complete keyboard and role pattern; a tooltip needs its own descriptive relationship and must not contain interactive controls.

Popover

Temporary overlaid content that lets users keep using the page: supplemental help, options, or actions. The mode determines when it disappears.

dialog

A dialog represents a dialog window. showModal() opens it in the top layer and makes the rest of the document inert; show() opens a non-modal one. Choose the variant that fits the task.

details

Content revealed in normal flow through a native summary: additional explanation or frequently asked questions. Open content participates in layout without requiring an overlay.

Styling and positioning

First preserve opening, reading, and dismissal. Then adjust presentation without changing the meaning of the pattern.

The browser hides a closed popover with display:none. Do not force display:block or grid in the closed state. Use :popover-open for open-state styles; if you need an internal layout, apply grid or flex to a wrapper inside the panel.

Default positioning is usually centered: popover does not place the panel next to the button by itself. The recipe keeps a viewport-constrained center, maximum height, and internal scrolling. To tether it to the invoker, evaluate CSS anchor positioning and edge alternatives with separate support checks; keep usable positioning as a fallback.

::backdrop represents the decorative layer immediately behind the popover. The recipe leaves it transparent. Darkening it does not make the panel modal; it may suggest blocking that does not exist. Check contrast, outside controls, and reading of content that remains relevant.

The examples add no animation. If you animate entry and exit, review @starting-style, display/overlay, and transition-behavior:allow-discrete as separate pieces. Keep a prefers-reduced-motion alternative and do not delay access to the close control for a visual effect.

Common mistakes

Connect the symptom to the failing contract before adding JavaScript.

The button does not open the panel

Expected result
The associated button toggles or shows its popover.
Cause
The target includes #, does not match the ID, is duplicated, or lacks the popover attribute.
Solution
Use a unique ID and that exact popovertarget value, without #. Inside forms, specify type="button" and check support.

The panel appears while closed

Expected result
Content overlays the page only when the native state is open.
Cause
An author rule overrides display:none, or the presence of the attribute is mistaken for the open state.
Solution
Preserve the browser's display and apply layout to a wrapper. Check state through :popover-open, rather than [popover] or an open attribute.

Escape does not close the reference

Expected result
The chosen mode provides the exit users need.
Cause
manual does not support auto's close requests or light dismiss. An unrecognized hint is also interpreted as manual in a browser with Popover.
Solution
Provide an accessible hide button. Use auto when you want light dismiss, and check hint compatibility before relying on its rules.

A visual menu does not work with the keyboard

Expected result
Interaction and accessible names match the content.
Cause
role="menu" was added assuming popover implemented arrow keys, items, and focus management.
Solution
Keep native links or buttons for a simple list of actions, or implement the full menu pattern when the product needs it.

The panel obscures its exit or sits far from the button

Expected result
Content and its exit remain visible on a narrow screen.
Cause
Top layer is confused with automatic positioning, or a fixed height cannot accommodate enlarged text.
Solution
Constrain size to the viewport, allow internal scrolling, and check the close control at 320 px and 200% text. Treat anchor positioning as a separate enhancement.

Support and compatibility

Check the core and the extensions your recipe uses; accepting an attribute does not validate the entire interaction.

Web Platform DX classifies Popover as Newly Available since January 27, 2025. The overall date is not each method or selector's date; the lab and examples do not certify every engine or assistive technology.

popover="hint" is listed as Limited availability in the October 8, 2026 consultation. When Popover exists but hint is unrecognized, the invalid value falls back to manual: you lose light dismiss and hint coexistence. Preserve an explicit exit or choose auto if those rules are necessary.

Detect the core with 'popover' in HTMLElement.prototype. For hint, create a test element, set its popover property to 'hint', and check that it returns 'hint', rather than 'manual'. Detection does not replace behavior tests; command/commandfor, source, interest invokers, anchors, and transitions have their own support.

If the overlay is only a convenience, keep content accessible in normal flow, as here, or use details when it fits the purpose. If it is essential, choose between a tested fallback and a compatible implementation. A handmade fixed panel does not reproduce the top layer or its focus and dismissal rules by itself.