Skip to content

HyPopover

Usage examples on this page are written for Lit / plain HTML (<hy-popover>). The same component ships as HyPopover in @whitespaceux/harmony-react (native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.

A non-modal overlay for contextual content, built on the Native Popover API (using popover="manual" mode) with Floating UI for automatic placement. Light dismiss (click outside, Escape key) is handled by the component itself for reliable cross-browser behavior.

Trigger Patterns

Slotted trigger (preferred): The trigger element is passed via the trigger slot, ensuring ARIA relationships work correctly across shadow DOM boundaries. The trigger automatically toggles the popover on click.

for attribute (fallback): When the trigger needs to be outside the popover element (e.g., in SSR frameworks), use the for attribute to reference a trigger element by ID. You'll need to add your own click handler to toggle the popover.

Positioning Technology

This component uses Floating UI for positioning instead of CSS Anchor Positioning. See the "Why Floating UI?" section in the component documentation for details on this architectural decision.

Popover vs Dialog

A popover is NOT a dialog. Use hy-popover for lightweight, non-modal, contextual overlays like tooltips, menus, and dropdowns. Use hy-dialog for modal interactions that require user attention and response.

Semantic Role

Unlike hy-dialog, popovers have no default ARIA role. Set the role attribute based on what the popover contains:

  • role="menu" - Action menus, context menus
  • role="listbox" - Selection dropdowns
  • role="tooltip" - Tooltips and toggletips
  • role="dialog" - Dialog-like content (prefer hy-dialog for true dialogs)
  • No role - Let content provide its own semantics (e.g., when wrapping a component that already has proper ARIA)

Accessibility Features

  • Full keyboard navigation (Escape to close)
  • Light dismiss (click outside to close)
  • ARIA live regions for screen reader announcements (opt-in)
  • Automatic ARIA relationships with trigger element:
    • For tooltips: aria-describedby (tooltip describes the trigger)
    • For other roles: aria-haspopup and aria-controls
  • Role-aware focus management:
    • Tooltips: Focus stays on trigger (tooltips are supplementary)
    • Other roles: Focus moves to popover on open, returns on close
    • The public focusSettled promise resolves once the open's initial-focus sequence has settled (deterministic await for tests)
  • High contrast mode support
  • Reduced motion support

Examples

Basic Usage (Slotted Trigger - Preferred)

html
<hy-popover>
  <button slot="trigger">Open</button>
  <p>Popover content</p>
</hy-popover>

Fallback Usage (for attribute)

html
<button id="trigger">Open</button>
<hy-popover id="my-popover" for="trigger">
  <p>Popover content</p>
</hy-popover>

<script>
  const trigger = document.getElementById('trigger');
  const popover = document.getElementById('my-popover');
  trigger.addEventListener('click', () => popover.toggle());
</script>

Placement Options

html
<hy-popover placement="top">
  <button slot="trigger">Open</button>
  Top positioned content
</hy-popover>

Without Arrow

html
<hy-popover no-arrow>
  <button slot="trigger">Open</button>
  <p>No arrow indicator</p>
</hy-popover>
html
<hy-popover role="menu" label="User actions">
  <button slot="trigger">Menu</button>
  <button role="menuitem">Edit Profile</button>
  <button role="menuitem">Settings</button>
  <button role="menuitem">Logout</button>
</hy-popover>

Tooltip Popover

html
<hy-popover role="tooltip" no-arrow>
  <span slot="trigger" tabindex="0">?</span>
  Press Enter to submit the form
</hy-popover>

Preventing Close

html
<hy-popover id="popover">
  <button slot="trigger">Open</button>
  Content
</hy-popover>
<script>
  document.getElementById('popover').addEventListener('request-hide', (e) => {
    if (hasUnsavedChanges) {
      e.preventDefault();
    }
  });
</script>

API

Properties

PropertyAttributeTypeDefaultDescription
openopenbooleanfalseControls the open state of the popover.
forforstring''The id of the element that anchors this popover. Use this as a fallback when the slotted trigger pattern isn't available (e.g., when the trigger needs to be outside the popover element).
placementplacement'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end''bottom'Where to place the popover relative to the anchor. Uses Floating UI with automatic fallback (flip, shift) when there isn't enough space.
distancedistancenumber8Distance between the popover and its anchor element in pixels. This creates a gap between the anchor and the popover.
skiddingskiddingnumber0Offset along the anchor's edge in pixels. Positive values move the popover in the direction of the text flow.
noArrowno-arrowbooleanfalseHides the arrow indicator when true.
labellabelstring''Accessible label for the popover. Applied as aria-label on the popover container for screen readers.
popoverIdpopover-idstring''Auto-generated unique identifier for the popover element. Used to establish ARIA relationships between trigger and popover.
announceStateChangesannounce-state-changesbooleanfalseWhether to announce state changes to screen readers via ARIA live regions. When enabled, announces "opened" and "closed" states.
announcementPriorityannouncement-priority'polite' | 'assertive''polite'ARIA live region politeness level for screen reader announcements. - polite: Announcements wait for current reading to complete (recommended) - assertive: Announcements interrupt current reading
rolerole'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | 'tooltip' | 'none'nullARIA role for the popover element. Should match the semantic purpose of the content.
describedBydescribed-bystring | undefinedID of an element that describes the popover content. Applied as aria-describedby on the popover container.
focusSettledPromise<void>Resolves once the current open's initial-focus sequence has settled — discovery ran and focus was applied, or the sequence was skipped (tooltip role, prevented request-show / initial-focus, closed before the deferred work ran). Before the first open it is already resolved.

Events

EventDetailDescription
request-showEmitted before opening; cancelable to prevent open.
showEmitted after the popover opens.
request-hideEmitted before closing; cancelable to prevent close.
hideEmitted after the popover closes.
initial-focusFired on show (non-tooltip role) before the autofocus discovery runs. Cancelable: call event.preventDefault() to skip the default focus and apply your own. Detail: HyInitialFocusDetail

Slots

SlotDescription
triggerThe element that triggers the popover (automatically toggles on click)
defaultThe content to display inside the popover.

CSS Parts

PartDescription
baseThe popover container element (has popover attribute).
bodyThe visible content wrapper with background and border.
arrowThe arrow indicator element.
live-regionThe ARIA live region for screen reader announcements (visually hidden).

CSS Custom Properties

PropertyDescription
--hy-popover-max-widthMaximum width of the popover
--hy-popover-paddingInner padding of the popover body
--hy-popover-radiusBorder radius
--hy-popover-strokeBorder width
--hy-popover-border-default-restBorder color
--hy-popover-ornamentSize of the arrow indicator
--hy-popover-shadowBox shadow for the popover body

Methods

show()

Shows the popover. Emits request-show before opening (cancelable). Emits show after opened.

hide()

Hides the popover. Emits request-hide before closing (cancelable). Emits hide after closed.

toggle()

Toggles the popover open/closed state. Emits the appropriate request event (cancelable) before toggling.

reposition()

Manually triggers a position update. Useful after content changes that affect popover size.

focus()

Sets focus on the popover element or its first focusable child.

blur()

Removes focus from the popover.

Built with Lit. Documented with VitePress.