Skip to content
HHydro UIdocs
Navigate
ComponentsPopover
ImplementedOverlay / non-modal disclosure

Popover

Anchor supplemental interactive content to a trigger without trapping focus.

Interactive

Try it

open, placement, strategy
AccountAccount Profile
Live previewReady
MarkupStateEvents
<hui-popover id="playground-popover" placement="bottom">
  <hui-popover-trigger>Account</hui-popover-trigger>
  <hui-popover-content aria-label="Account actions">
    <strong>Account</strong>
    <a data-autofocus href="#profile">Profile</a>
    <button type="button">Sign out</button>
  </hui-popover-content>
</hui-popover>
data-state
Waiting for enhancement

No component events yet.

Usage

Use hui-popover for supplemental, interactive content that is anchored to a trigger but does not require modal focus containment. The native Popover API is the preferred substrate when available; the component keeps a positioning and light-dismiss fallback for browsers without it.

Anatomy

  • Root: hui-popover
  • Trigger: hui-popover-trigger renders a native button
  • Content: hui-popover-content renders an overlay with role="dialog" by default
  • Trigger semantics: aria-haspopup="dialog", aria-expanded, and guarded aria-controls
  • State hooks: root and content use data-state="open|closed"
  • Placement hook: content uses data-placement="top|bottom|left|right"

Examples

Fixture exampleSource-backed
<section aria-labelledby="popover-heading">
    <h2 id="popover-heading">Account actions</h2>
    <div class="popover-clip">
      <hui-popover id="account-popover" placement="bottom">
        <hui-popover-trigger><b>Account</b></hui-popover-trigger>
        <hui-popover-content aria-label="Account">
          <h3 id="account-popover-title">Account</h3>
          <a data-autofocus href="#profile">Profile</a>
          <button type="button">Sign out</button>
        </hui-popover-content>
      </hui-popover>
    </div>
    <hui-popover id="manual-popover" strategy="manual" placement="top">
      <hui-popover-trigger>Manual fallback</hui-popover-trigger>
      <hui-popover-content>Fallback content is positioned outside clipped ancestors.</hui-popover-content>
    </hui-popover>
  </section>

API reference

Root attributes

AttributeMeaning
openOpens the popover. It is the state source of truth and is updated by the trigger and dismiss actions.
default-openSeeds open when no explicit open state is present.
placementPreferred placement: top, bottom (default), left, or right. Collision handling may choose another side.
strategynative (default) uses the Popover API when available; manual always uses the fallback path.
data-stateEnhanced state: open or closed, normally managed by the component.

Events:

  • hui-open-change: emitted by the root with { open: boolean }.

Content attributes

  • role="dialog" is the default; role="none" can opt out of dialog semantics.
  • aria-label and aria-labelledby name the content when it is not otherwise labelled.

States and styling

StateContract and styling guidance
ClosedContent has hidden after any allowed exit; during exit it is aria-hidden, inert, and non-interactive.
OpenContent is visible, positioned, and the trigger has aria-expanded="true".
Placementdata-placement exposes the resolved collision-aware side for consumer transitions.
FocusFocus remains free to move into and out of the non-modal content; [data-autofocus] receives focus when present.
State channel

Attributes are the source of truth. Style derived state with data-state, ARIA attributes, and the --hui-* token aliases.

Accessibility and keyboard support

  • Trigger click toggles open.
  • Escape and outside pointer/focus dismiss the popover.
  • Native Popover API light-dismiss is used when supported; the fallback listens for outside pointer and focus events.
  • Focus moves to [data-autofocus] inside the content when opening; no focus trap is installed.
  • Focus returns to the trigger when the popover is dismissed if focus was in the content.
  • Placement uses collision-aware positioning and writes --hui-position-x and --hui-position-y.
  • strategy="manual" exercises the same semantics without relying on the native Popover API.

When motion is allowed, the native or manual surface remains present through its tokenized exit. Native hidePopover() and manual portal cleanup run after the exit completes; reduced-motion and no-transition environments close immediately.

SSR and forms

SSR serializes the trigger ARIA contract and content with popover plus inline hidden when closed. IDs are derived from the root id; id-less roots omit aria-controls rather than emit a dangling reference. Client adoption regenerates only component template markup and preserves projected content identity. Without JavaScript, the closed popover remains hidden and the trigger is a native button with no active popup behavior.

Code

<hui-popover id="account-popover" placement="bottom">
  <hui-popover-trigger>Account</hui-popover-trigger>
  <hui-popover-content aria-labelledby="account-popover-title">
    <h2 id="account-popover-title">Account</h2>
    <a data-autofocus href="/profile">Profile</a>
  </hui-popover-content>
</hui-popover>
<hui-popover id="playground-popover" placement="bottom">
  <hui-popover-trigger>Account</hui-popover-trigger>
  <hui-popover-content aria-label="Account actions">
    <strong>Account</strong>
    <a data-autofocus href="#profile">Profile</a>
    <button type="button">Sign out</button>
  </hui-popover-content>
</hui-popover>