Skip to content

Popover

hc-popover styles the native popover attribute. The browser handles open/close, top-layer rendering, light-dismiss, and Escape-to-close — no JavaScript required.

Also known as: floating panel, anchored overlay.

PrimitiveRequired version
HTML popoverChrome 114, Edge 114, Firefox 125, Safari 17
CSS Anchor PositioningChrome 125, Edge 125, Firefox 147, Safari 26

On engines without Anchor Positioning, installPopover()’s JS fallback positions a data-side popover; without JavaScript it stays browser-centred. The data-arrow pointer is plain CSS and renders everywhere.

A short, focused message. The browser closes me on Escape or by clicking outside.

The popover attribute defaults to popover="auto", which means:

  • Light-dismiss on outside click.
  • Escape-to-close.
  • Only one auto popover is open at a time.

For a popover that ignores light-dismiss, use popover="manual" and close it yourself with element.hidePopover().

The basic light-dismiss popover above works from the native Popover API alone. The directional placement and htmx auto-close features below need behaviors — install the ones you use once at startup:

import { installPopover, installClosePopover } from '@hypermedia-components/core';
installPopover(); // data-side / data-align anchoring + aria sync
installClosePopover(); // close on a successful htmx request (optional)

The zero-config @hypermedia-components/core/behaviors entry installs both automatically.

A bare popover is positioned by the browser (centred in the top layer). To anchor it to its trigger with a side and alignment, add data-side (top / right / bottom / left) and optionally data-align (start / center / end, default center). installPopover() wires the anchoring and keeps aria-expanded / aria-controls on the trigger in sync. Add data-arrow for a small pointer.

Opens to the inline-end, top-aligned.

Placement uses CSS Anchor Positioning (position-area) where supported and position-try-fallbacks to flip at the viewport edge. The same attributes drive a JS fallback (getBoundingClientRect) for engines without it, so both paths place the popover identically. The shared mechanics — both paths, the arrow, and the --hc-anchored-* knobs — are documented in Fundamentals → Anchored positioning.

See Browser baseline below for the engine requirements and the fallback behavior.

A popover can host an htmx-driven form. To close the popover after a successful request, add the data-hc-close-popover-on-success attribute and install the installClosePopover() behavior (bundled in @hypermedia-components/core/behaviors).

<form
data-hx-get="/items"
data-hx-target="#results"
data-hc-close-popover-on-success>
</form>

The behavior listens for htmx:afterRequest, checks for success, and calls closest('[popover]').hidePopover().

Form-bearing popovers (the datagrid filter / sort / column panels) use two optional parts: hc-popover__body stacks the controls (display: grid with --hc-popover-body-gap — it also resets a <fieldset>, the natural body element for a set of checkboxes), and hc-popover__footer is the trailing action row (end-aligned, gapped). A bare popover needs neither.

<div id="cols-popover" class="hc-popover" popover>
<form data-hx-get="/items" data-hx-target="#items-grid"
data-hc-close-popover-on-success>
<fieldset class="hc-popover__body">
<label class="hc-checkbox-label">
<input class="hc-checkbox" type="checkbox" name="cols" value="name" checked />
Name
</label>
<!-- …one checkbox per column… -->
</fieldset>
<footer class="hc-popover__footer">
<button class="hc-button" type="submit" data-variant="primary">Apply</button>
</footer>
</form>
</div>
  • A popover is not automatically a menu. Setting role="menu" and providing arrow-key navigation are separate concerns documented elsewhere.
  • The trigger (the element with popovertarget) gets focus back when the popover closes — this is browser behavior.
  • For tooltip-like popovers triggered by hover or focus, use the [popovertargetaction="show"] attribute family or a custom hover handler. Hover-only tooltips are not accessible to keyboard users by themselves.
  • The popover’s accessible name comes from its content; if the popover has no visible title, add aria-label.
Token pathPurpose
popover.bg / -fg / -borderSurface colors.
popover.radiusBorder radius.
popover.paddingInner padding.
popover.min-width / -max-widthSize bounds.
popover.body-gap / -footer-gapGaps inside __body / __footer (the body gap also separates the footer from the body).
Show the generated CSS variables
--hc-popover-bg | -fg | -border
--hc-popover-radius | -padding
--hc-popover-min-width | -max-width
--hc-popover-body-gap | -footer-gap

Used in recipes: Filter popover