Skip to content

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

Click to open

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-starttop-start); after alignment it is still clamped to the viewport.

Four directions

UpDownLeftRight

12-way placement (-start / -end)

bottom-startbottom-endright-starttop-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

Hover to openClick or hoverRight-click to open

Custom open/close delays

hover-delay=400hover-hide-delay=400

Generic delays (open-delay / close-delay)

open-delay=400close-delay=400

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

Disabled (click)Disabled (hover)

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)

width="280"width="trigger" (220px)width="50%"

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)

offset="12, 20"offset="16"

Collision tuning (collision-padding / fallback-placements / hide-when-detached)

collision-padding=20fallback-placementshide-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)

Open (focus lands in the input)

On open, focus goes straight into the input below:

Open with keys (trigger-keys)

Focus then press Enter

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)

Open portal panel

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

DefaultNo arrowPoint at center

Merged arrow (arrow-merge)

bottom-start mergedReference

Auto adjust disabled

Auto adjust off

Custom content

Custom content (slot=content)

Open panel
You can place arbitrary custom content via 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

closableClose from a panel button
Got it

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)

primarysuccesswarningdanger

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

Try itRight expand

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

Content → v1Content → v2 (closed)content: -Open to see the content

auto-close: closes after a timeout

Open (closes after 3s)

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 main card

Open a child layer from within the parent panel:

Open child card

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)

Open at (160, 90)Closeopen: false

Virtual triggering (anchor element)

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)

Open modal panel

Focus is trapped inside the panel:

DoneCancel

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)

OpenCloseopen: falseTrigger element

API

Attributes

AttributeDescriptionTypeDefault
append-toPortal 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
arrowWhether to show the arrow (default true; arrow="false" hides it, the element and ::part(arrow) are kept)stringtrue
arrow-mergeMerge 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 unaffectedboolean
arrow-point-at-centerMake 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-overflowViewport-edge auto flip and avoidance (default true; "false" disables it, keeping the declared placement, which may overflow the viewport)stringtrue
auto-closeAuto close after opening for the given duration in ms, e.g. auto-close="3000"; not set means no auto closestring
closableShow a close button at the panel's top-right (part="close"); clicking it closes and restores focus to the triggerboolean
close-delayGeneric close delay in ms (default 0; used by non-hover trigger paths, hover paths prefer hover-hide-delay)string
collision-paddingViewport-edge clamping padding in px (default 4), the gap kept when the panel avoids viewport edgesstring
colorColor variant: primary / success / warning / danger (tinted panel background + semantic border, derived from tokens including dark variants); unset or invalid keeps the neutral panelstring
contentBody textstring
disabledDisable the whole popover: click / hover / focus / contextmenu / trigger-keys triggers are all ignored; the host is desaturated and aria-disabled is syncedboolean
fallback-placementsCustom 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 flipstring
focus-on-openMoves focus into the first focusable element of the panel when openedboolean
freshKeep 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-detachedHide the panel when the anchor is fully detached from the viewport (the open state is kept, avoiding a panel floating off-screen)boolean
hover-delayHover-trigger open debounce in ms (default 150; falls back to open-delay when unset)string
hover-hide-delayHover-trigger close debounce in ms (default 100; falls back to close-delay when unset)string
initial-focusFocus 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-openstring
modalModal mode: full-screen backdrop + focus trap (Tab cycles inside the panel) + scroll lock + aria-modal; clicking the backdrop closesboolean
offsetDual-axis offset: "main distance" or "main distance, cross offset" in px (default 8, 0), e.g. offset="12, 20"
openControlled display (boolean attribute; shows when present)boolean
open-delayGeneric open delay in ms (default 0; used by non-hover trigger paths, hover paths prefer hover-delay)string
placementPopup placement (12 directions: four bases top/bottom/left/right each with -start/-end cross-axis alignment)stringtop
titleTitle textstring
triggerTrigger method: click (default) / hover / focus / contextmenu / manual, space separated for multiple (e.g. "click hover")stringclick
trigger-keysToggle open when the listed keys are pressed while the trigger is focused (space separated, e.g. "Enter Space"); no key binding when unsetstring
virtualVirtual trigger mode (same as tooltip; no anchor element)boolean
virtual-anchorVirtual anchor element selector (used when virtual-x/virtual-y are unset)
virtual-xVirtual anchor x (viewport coordinate, px)
virtual-yVirtual anchor y (viewport coordinate, px)
widthPanel width: a number (px) / "trigger" (same width as the trigger) / any CSS value (e.g. 50%, 240px); unset keeps the defaultstring

Events

EventDescription
oas-open-changeopen state changed, detail: { open }

Slots

NameDescription
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.

Released under the MIT OR Apache-2.0 License.