Popover
Anchor supplemental interactive content to a trigger without trapping focus.
Try it
<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-triggerrenders a nativebutton - Content:
hui-popover-contentrenders an overlay withrole="dialog"by default - Trigger semantics:
aria-haspopup="dialog",aria-expanded, and guardedaria-controls - State hooks: root and content use
data-state="open|closed" - Placement hook: content uses
data-placement="top|bottom|left|right"
Examples
<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
| Attribute | Meaning |
|---|---|
open | Opens the popover. It is the state source of truth and is updated by the trigger and dismiss actions. |
default-open | Seeds open when no explicit open state is present. |
placement | Preferred placement: top, bottom (default), left, or right. Collision handling may choose another side. |
strategy | native (default) uses the Popover API when available; manual always uses the fallback path. |
data-state | Enhanced 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-labelandaria-labelledbyname the content when it is not otherwise labelled.
States and styling
| State | Contract and styling guidance |
|---|---|
| Closed | Content has hidden after any allowed exit; during exit it is aria-hidden, inert, and non-interactive. |
| Open | Content is visible, positioned, and the trigger has aria-expanded="true". |
| Placement | data-placement exposes the resolved collision-aware side for consumer transitions. |
| Focus | Focus remains free to move into and out of the non-modal content; [data-autofocus] receives focus when present. |
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. Escapeand 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-xand--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>