Popover
A click / hover / focus triggered popup panel that can hold a title, body text and arbitrary custom content. Supports 12-way placement, dual-axis offset, open/close animation, portal, modal mode and nested popovers.
Basic usage
Trigger on click
Placement
placement supports 12 directions: the four bases top / bottom / left / right each with -start / -end cross-axis alignment (bottom-start aligns the panel's left edge with the trigger's left edge, the most common form). When space is insufficient the panel flips along the main axis and keeps the alignment suffix (bottom-start → top-start); after alignment it is still clamped to the viewport.
Four directions
12-way placement (-start / -end)
Trigger methods
trigger controls the trigger method: click (default) / hover / focus / contextmenu / manual, space separated for multiple (e.g. "click hover"). For hover triggering, hover-delay / hover-hide-delay control the open/close debounce (default 150 / 100ms; without debounce hover flickers open/closed); the hover area is the trigger plus the panel (moving across the gap does not close it). manual mode binds no host events at all — visibility is fully controlled by the host open attribute.
Hover trigger
Custom open/close delays
Generic delays (open-delay / close-delay)
Disabled
disabled disables the whole popover: click / hover / focus / right-click / key triggers are all ignored; the host is desaturated (opacity .6) and aria-disabled is synced. Disabled trigger elements (such as native disabled buttons) do not dispatch mouse events — wrap them in a span before attaching a popover.
Whole popover disabled
Width
width controls the panel width: a number (px), "trigger" (same width as the trigger) or any CSS value (e.g. 50%). width="trigger" fits the "panel as wide as the control" dropdown-select shape.
Width (width)
Offset and collision tuning
offset is a dual-axis offset: "main distance" or "main distance, cross offset" (default 8, 0). Collision tuning: collision-padding viewport clamp gap (default 4px); fallback-placements custom fallback sequence (when the requested placement does not fit, each candidate is tried in order); hide-when-detached hides the panel when the anchor is fully outside the viewport.
Dual-axis offset (offset)
Collision tuning (collision-padding / fallback-placements / hide-when-detached)
Initial focus and keyboard
focus-on-open moves focus into the first focusable element of the panel when opened; initial-focus precisely focuses the element matched by a selector (higher priority, falls back to focus-on-open when unresolvable). trigger-keys toggles open when the listed keys are pressed while the trigger is focused (space separated).
Specified initial focus (initial-focus)
On open, focus goes straight into the input below:
Open with keys (trigger-keys)
Portal
append-to moves the panel outside the host container (to body or a selector), avoiding clipping by the host's overflow: hidden / clip; positioning is viewport-based and unaffected by the move. After the panel leaves the shadow, clicks inside the panel still do not trigger outside-click close.
Portal (append-to)
Arrow and viewport auto adjust
By default an arrow pointing at the trigger element's edge is shown; arrow="false" hides the arrow; arrow-point-at-center makes the arrow point at the trigger element's center (when the panel is shifted by viewport-edge avoidance, the arrow still points at the anchor center). By default the panel automatically flips along the main axis and avoids the viewport edges when space is insufficient; auto-adjust-overflow="false" disables the auto adjust so the panel keeps the declared placement (it may overflow the viewport).
Arrow visibility and pointing
Merged arrow (arrow-merge)
Auto adjust disabled
Custom content
Custom content (slot=content)
slot="content". Close button and declarative close
closable shows a close button at the panel's top-right (part="close"); clicking it closes and restores focus. Any element inside the content with data-popover="close" becomes a declarative close control — clicking it closes the popover (great for "Done" / "Got it" style action buttons).
Close button and declarative close
Color variants
color semantic variants: primary / success / warning / danger — a tinted panel background plus a semantic border (arrow included), all derived from tokens (auto-adapted to the dark theme).
Color variants (color)
Open/close animation
The panel plays a fade + scale animation on open and close; transform-origin is direction-aware (expanding from the edge that faces the trigger, hugging the aligned edge for -start/-end). Animations are disabled under prefers-reduced-motion.
Direction-aware animation
Live content and auto close
By default (without fresh) the content is frozen while closed and the latest value is written when opened; with fresh the closed state keeps syncing content (avoids flicker for controlled content). auto-close auto closes after the given duration (onboarding / guided-tour scenarios).
fresh: content keeps updating while closed
auto-close: closes after a timeout
Nested popovers
A floating layer can open a child popover / tooltip from its own content: the child layer anchors correctly and stacks above the parent; closing the parent closes the child as well; Esc closes one layer at a time and restores focus each time.
Nested popovers (pop from a card)
Open a child layer from within the parent panel:
Virtual triggering
In virtual mode there is no real anchor (same as tooltip): the host positions the popover with virtual-x / virtual-y viewport coordinates or a virtual-anchor element selector, ideal for chart / canvas coordinate hints; open controls visibility. In virtual mode neither clicking the trigger nor clicking outside changes the state — the lifecycle is fully host-controlled.
Virtual triggering (canvas cursor tracking)
Move the mouse to see coordinate hints
Virtual triggering (anchor coordinates)
Virtual triggering (anchor element)
Modal mode
modal turns the popover into a modal floating layer: full-screen backdrop + focus trap (Tab / Shift+Tab cycle inside the panel, escaped focus is pulled back, only the topmost modal traps) + scroll lock (intercepts wheel / scroll keys while keeping the scrollbar visible) + aria-modal. Clicking the backdrop closes and restores focus.
Modal mode (backdrop + focus lock + scroll lock)
Focus is trapped inside the panel:
Controlled display
The open attribute is controlled: an external button can set/remove open to control visibility (clicking outside / pressing Esc still closes it).
Controlled display (open attribute)
API
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
append-to | Portal mount target: the panel moves into the target container (body or a CSS selector) when opened and moves back into the host shadow when closed; for panels clipped by host containers (overflow) | string | — |
arrow | Whether to show the arrow (default true; arrow="false" hides it, the element and ::part(arrow) are kept) | string | true |
arrow-merge | Merge the arrow with the panel corner (C1): a right triangle merges flush with the panel corner — legs collinear with the panel edges (their strokes continue the panel border band), tip pointing orthogonally toward the anchor; the matching corner radius is zeroed; only takes effect for -start/-end placements, center placements are unaffected | boolean | — |
arrow-point-at-center | Make the arrow point at the trigger element's center (default points at the trigger's edge; the arrow still points at the anchor center when the panel is shifted by viewport-edge avoidance) | — | — |
auto-adjust-overflow | Viewport-edge auto flip and avoidance (default true; "false" disables it, keeping the declared placement, which may overflow the viewport) | string | true |
auto-close | Auto close after opening for the given duration in ms, e.g. auto-close="3000"; not set means no auto close | string | — |
closable | Show a close button at the panel's top-right (part="close"); clicking it closes and restores focus to the trigger | boolean | — |
close-delay | Generic close delay in ms (default 0; used by non-hover trigger paths, hover paths prefer hover-hide-delay) | string | — |
collision-padding | Viewport-edge clamping padding in px (default 4), the gap kept when the panel avoids viewport edges | string | — |
color | Color variant: primary / success / warning / danger (tinted panel background + semantic border, derived from tokens including dark variants); unset or invalid keeps the neutral panel | string | — |
content | Body text | string | — |
disabled | Disable the whole popover: click / hover / focus / contextmenu / trigger-keys triggers are all ignored; the host is desaturated and aria-disabled is synced | boolean | — |
fallback-placements | Custom fallback sequence (comma or space separated, e.g. "left, right"): when the requested placement does not fit, each candidate in the sequence is tried for fit; the first fit wins, if none fit the last one is clamped; unset uses the default main-axis flip | string | — |
focus-on-open | Moves focus into the first focusable element of the panel when opened | boolean | — |
fresh | Keep updating the content while closed (by default the content is frozen while closed and the latest value is written when opened; with fresh the closed state keeps writing) | boolean | — |
hide-when-detached | Hide the panel when the anchor is fully detached from the viewport (the open state is kept, avoiding a panel floating off-screen) | boolean | — |
hover-delay | Hover-trigger open debounce in ms (default 150; falls back to open-delay when unset) | string | — |
hover-hide-delay | Hover-trigger close debounce in ms (default 100; falls back to close-delay when unset) | string | — |
initial-focus | Focus the element matched by the selector when opened (host light DOM first, including slot content; falls back to focus-on-open when unresolvable), higher priority than focus-on-open | string | — |
modal | Modal mode: full-screen backdrop + focus trap (Tab cycles inside the panel) + scroll lock + aria-modal; clicking the backdrop closes | boolean | — |
offset | Dual-axis offset: "main distance" or "main distance, cross offset" in px (default 8, 0), e.g. offset="12, 20" | — | — |
open | Controlled display (boolean attribute; shows when present) | boolean | — |
open-delay | Generic open delay in ms (default 0; used by non-hover trigger paths, hover paths prefer hover-delay) | string | — |
placement | Popup placement (12 directions: four bases top/bottom/left/right each with -start/-end cross-axis alignment) | string | top |
title | Title text | string | — |
trigger | Trigger method: click (default) / hover / focus / contextmenu / manual, space separated for multiple (e.g. "click hover") | string | click |
trigger-keys | Toggle open when the listed keys are pressed while the trigger is focused (space separated, e.g. "Enter Space"); no key binding when unset | string | — |
virtual | Virtual trigger mode (same as tooltip; no anchor element) | boolean | — |
virtual-anchor | Virtual anchor element selector (used when virtual-x/virtual-y are unset) | — | — |
virtual-x | Virtual anchor x (viewport coordinate, px) | — | — |
virtual-y | Virtual anchor y (viewport coordinate, px) | — | — |
width | Panel width: a number (px) / "trigger" (same width as the trigger) / any CSS value (e.g. 50%, 240px); unset keeps the default | string | — |
Events
| Event | Description |
|---|---|
oas-open-change | open state changed, detail: { open } |
Slots
| Name | Description |
|---|---|
| default | — |
content | — |
Clicking the trigger toggles visibility; clicking outside or pressing Esc closes it; role="dialog". Nested popovers: closing the parent cascades to children; Esc closes one layer at a time and restores focus to the trigger.