Under The Hood
frontend css Popover API (Chrome 114+, Firefox 125+, Safari 17+)

Popover API: Top Layer Promotion, Light-Dismiss, Backdrop

Last updated
Prerequisites:
HTML semantics and ARIA basics
Understanding of stacking contexts and z-index
  • css
  • popover
  • top-layer
  • dialog
  • light-dismiss
  • backdrop
  • html

Read at your depth

The practical view

The popover attribute turns an element into a popover: <div id="menu" popover>...</div>. Toggle it with a button: <button popovertarget="menu">Open</button>. Popovers are promoted to the top layer, so they always paint above everything regardless of z-index. They support light-dismiss: clicking outside or pressing Escape closes them automatically. Style with the :popover-open pseudo-class and the ::backdrop pseudo-element. The popover API removes the need for custom overlay JS/ARIA wiring for simple menus, tooltips, and toasts: browser handles focus, dismissal, and top-layer stacking.

The same idea in other frameworks

react equivalent
React overlays can use the native popover attribute + popovertarget instead of portal+backdrop for simple menus.

Legacy vs modern

Custom overlay with z-index vs native popover top layer

Hand-rolled overlays fight z-index and need manual dismissal logic; the popover API handles top-layer placement and light-dismiss natively.

before → after
Custom overlay + z-index management
<div class="menu" hidden>...</div>
.menu { position: fixed; z-index: 9999; } /* ❌ OLD: z-index arms race */
Native popover attribute
<button popovertarget="menu">Open</button>
<div id="menu" popover>Menu items...</div> <!-- ✅ NEW: top layer + light dismiss -->

Interview gotchas

Context

This tests the rendering model — interviewers want you to distinguish the top layer (a separate rendering surface above all stacking contexts) from merely-high z-index values.

The mechanical answer

The top layer is a rendering layer above the document's root stacking context: an element promoted to it paints above every other element regardless of z-index, because z-index only orders elements within a stacking context, and the top layer is not part of any of them. The browser re-parents the popover's rendering into this layer (its containing block becomes the viewport), so it cannot be trapped by an ancestor's stacking context or overflow clipping. This is the same mechanism <dialog> and fullscreen elements use. When a popover opens, it is promoted; when it closes, it is demoted. This is why popovers survive inside transformed/overflow-hidden cards that would otherwise clip or trap them.

Trap

The naive answer is 'popover just has an infinite z-index'. The top layer is structurally above all stacking contexts; z-index: 999999 inside a transformed ancestor still loses to the top layer. Another trap: assuming multiple popovers stack in the top layer by z-index — the browser manages topmost-popover ordering (the last opened is topmost, and light dismiss targets it). Candidates who explain the re-parenting and stacking-context independence show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.