🚀AdvancedAdvanced Interactive Elements

Popover API

Use the native HTML popover attribute to build tooltips, dropdowns, and flyouts that live in the top layer without JavaScript.

Overview

The Popover API (shipping in all modern browsers since 2024) lets you create tooltips, dropdowns, menus, and flyout panels using only HTML attributes no JavaScript required for basic show/hide behavior. Any element with the `popover` attribute becomes a popover: it is hidden by default and placed in the top layer when opened. Popovers are connected to trigger buttons via the `popovertarget` attribute pointing at the popover's id. The `popovertargetaction` attribute controls whether the button shows, hides, or toggles the popover. Unlike `<dialog>`, the default popover type (`auto`) has light-dismiss behavior clicking outside or pressing Escape closes it automatically. The `manual` type requires explicit JS to close. The Popover API complements the Dialog element: use dialog for critical interactions that need a focus trap; use popover for supplementary UI like tooltips, context menus, and notification banners.

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.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What is the default behavior of a popover with popover="auto"?