Popover

A native popover built with the popover attribute and declarative popovertarget buttons. The browser handles the top layer, light dismiss, Escape closing, and open state without custom JavaScript.

Popover component illustration

0 KB JavaScript

~40 CSS lines

popover declarative controls

dismiss native light dismiss

Preview

The code

The markup

<button type="button" popovertarget="info-popover">
  Open popover
</button>

<div id="info-popover" popover="auto" class="popover" aria-labelledby="popover-title">
  <div class="popover-inner">
    <button class="popover-close" type="button" popovertarget="info-popover" popovertargetaction="hide" aria-label="Close">×</button>

    <h2 id="popover-title">Contextual popover</h2>

    <!-- content -->

    <button type="button" popovertarget="info-popover" popovertargetaction="hide">Got it</button>
  </div>
</div>

CSS mechanisms

/* ----------------------------------------------------------------------------
 * Popover
 * ---------------------------------------------------------------------------- */

/**
 * Native popover panel.
 *
 * The popover element stays visually neutral and moves into the
 * top layer when opened. Use `.popover-inner` for the visible
 * surface and custom presentation styles.
 */

.popover {
  margin: auto;
  overflow: visible;
  /* add your styles here */
}

/**
 * Visible popover content wrapper.
 * Use this element for custom spacing, surface, border, shadow, etc.
 */

.popover-inner {
  /* add your styles here */
}

/* ----------------------------------------------------------------------------
 * Popover close button
 * ---------------------------------------------------------------------------- */

/**
 * Explicit close control.
 */

.popover-close {
  /* add your styles here */
}

/* ----------------------------------------------------------------------------
 * Popover Animation (Top Layer)
 * ---------------------------------------------------------------------------- */

 /**
 * Closed-state styling for the popover panel.
 *
 * Uses the new `transition-behavior` / `allow-discrete` mechanism
 * so the `display: none → block` and top-layer `overlay` changes
 * animate smoothly instead of snapping. Without `allow-discrete`,
 * the popover would appear and disappear instantly.
 */

.popover {
  opacity: 0;
  transition:
    opacity 240ms,
    overlay 240ms allow-discrete,
    display 240ms allow-discrete;
}

/**
 * Open state: full opacity.
 *
 * `:popover-open` only matches the popover while it is showing.
 */

.popover:popover-open {
  opacity: 1;
}

/**
 * Initial keyframe override: required so the entrance transition
 * runs on the first open after the discrete `display` change.
 */

@starting-style {
  .popover:popover-open {
    opacity: 0;
  }
}

/**
 * Backdrop transition hook.
 *
 * Add a background here if you want a visible overlay.
 */

.popover::backdrop {
  opacity: 0;
  transition:
    opacity 240ms,
    overlay 240ms allow-discrete,
    display 240ms allow-discrete;
}

/**
 * Open backdrop becomes visible.
 */

.popover:popover-open::backdrop { 
  opacity: 1; 
}

/**
 * Initial keyframe override for the backdrop, same reason as above.
 */

@starting-style {
  .popover:popover-open::backdrop { 
    opacity: 0; 
  }
}

Browser support

Benefits

Current limitations