Skip to content

Popover

An interactive, non-modal popup built on the native Popover API with focus management, ARIA wiring, and anchored positioning.

<pe-popover data-popover-offset="8">
<button type="button" class="Trigger" data-popover-trigger>Account</button>

<div class="Popup" data-popover-content hidden>
  <span class="Arrow" data-popover-arrow></span>
  <div class="Header">
    <h2 class="Title" data-popover-title>Account</h2>
    <button type="button" class="Close" data-popover-close>Close</button>
  </div>
  <p class="Description" data-popover-description>Manage your current session.</p>
</div>
</pe-popover>

Use Popover for interactive, non-modal content. Use Tooltip for short hints and Dialog for modal tasks. Keep hidden on authored content and provide an accessible name.

Part Selector Role
Host <pe-popover> Lifecycle host for one popup
Trigger [data-popover-trigger] Toggles the popup
Content [data-popover-content] Native manual popover with role="dialog"
Arrow [data-popover-arrow] Optional decorative direct child of content; receives positioning state and coordinates
Title [data-popover-title] Accessible name source
Description [data-popover-description] Accessible description source
Close [data-popover-close] Closes the popup
<button type="button" data-popover-trigger="filters-popover">Filters</button>
<pe-popover id="filters-popover">
<div data-popover-content hidden aria-label="Filters">
...
</div>
</pe-popover>

Use data-popover-initial-focus to override the first focusable target.

Keep the child host inside the parent content. Native popover nesting and the managed stack ensure that Escape or an outside press closes the child before its parent.

<pe-popover>
<button type="button" class="Trigger" data-popover-trigger>Open parent</button>
<div class="Popup" data-popover-content hidden>
  <div class="Header">
    <h2 class="Title" data-popover-title>Parent popover</h2>
    <button type="button" class="Close" data-popover-close>Close parent</button>
  </div>

  <pe-popover data-popover-side="right" data-popover-offset="8">
    <button type="button" class="Trigger" data-popover-trigger>Open child</button>
    <div class="Popup Nested" data-popover-content hidden>
      <div class="Header">
        <h2 class="Title" data-popover-title>Child popover</h2>
        <button type="button" class="Close" data-popover-close>Close child</button>
      </div>
    </div>
  </pe-popover>
</div>
</pe-popover>
NameDescription
data-popover-triggerMarks a trigger. Empty value targets an internal popover. A host id string targets an external popover.
data-popover-contentThe popover surface. Receives `popover="manual"` and positioning styles.
data-popover-arrowMarks the optional decorative arrow. Must be a direct child of data-popover-content.
data-popover-closeInside content: closes the popover when activated.
data-popover-titlePrimary label source for content `aria-labelledby`.
data-popover-descriptionDescription source for content `aria-describedby`.
data-popover-initial-focusInside content: preferred focus target on open. Defaults to the first focusable element.
NameTypeDescription
isOpenboolean (readonly)Reads whether the popover is currently open.
open(trigger?)voidOpens and positions the popover for a trigger.
close()voidCloses the popover programmatically.
toggle(trigger?)voidOpens, closes, or changes the active trigger.
NameDescription
data-popover-sidePreferred side: top | right | bottom | left. Default: bottom.
data-popover-alignAlignment: start | center | end. Default: center.
data-popover-offsetDistance from the trigger in pixels. Default: 0.
data-popover-align-offsetCross-axis offset in pixels. Default: 0.
data-popover-collision-paddingViewport padding for flip and clamp. Default: 5.
data-popover-arrow-paddingMinimum distance between an authored arrow and the content edges. Default: 5.
data-stateOn host/content, arrow, and triggers: "open" or "closed". With shared triggers, only the active trigger is open.
data-side / data-alignResolved placement on content and arrow after collision handling.
data-uncenteredPresent on the arrow when edge padding prevents it from centering on the trigger.
NameDescription
pe-popover:openFired on open. detail includes content, trigger, and reason.
pe-popover:closeFired on close. detail includes content, trigger, and reason.

Close reasons: trigger, close-control, dismiss, escape, programmatic, native.

Content is placed in the browser top layer with fixed inline coordinates. All visual styling remains yours.

.Popup {
width: min(20rem, calc(100vw - 1rem));
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
overflow: visible;
background: oklch(97% 0 0);
}

An optional [data-popover-arrow] must be a decorative direct child of the content. Set its size, shape, and side styles in CSS; include its size in data-popover-offset.

While open, the content surface also receives positioning CSS variables:

Name Description
--anchor-width / --anchor-height Active trigger dimensions
--available-width / --available-height Remaining viewport space on the resolved side
--positioner-width / --positioner-height Current content dimensions (updated on resize)
--transform-origin Anchor-relative origin for enter/exit transitions

Content also receives data-starting-style and data-ending-style for enter and exit animations.