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.
Browser baseline
Section titled “Browser baseline”| Primitive | Required version |
|---|---|
HTML popover | Chrome 114, Edge 114, Firefox 125, Safari 17 |
| CSS Anchor Positioning | Chrome 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.
Basic HTML
Section titled “Basic HTML”A short, focused message. The browser closes me on Escape or by clicking outside.
<button class="hc-button" type="button" popovertarget="filter-popover"> Filter</button>
<div id="filter-popover" class="hc-popover" popover> <p>Choose filters here.</p></div>The popover attribute defaults to popover="auto", which means:
- Light-dismiss on outside click.
- Escape-to-close.
- Only one
autopopover is open at a time.
For a popover that ignores light-dismiss, use popover="manual" and
close it yourself with element.hidePopover().
JavaScript
Section titled “JavaScript”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 syncinstallClosePopover(); // close on a successful htmx request (optional)The zero-config @hypermedia-components/core/behaviors entry installs both
automatically.
Directional placement
Section titled “Directional placement”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.
<button class="hc-button" type="button" popovertarget="filters">Filter</button>
<div id="filters" class="hc-popover" popover data-side="right" data-align="start" data-arrow> <p style="margin:0;">Opens to the inline-end, top-aligned.</p></div>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.
htmx usage
Section titled “htmx usage”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().
Panel structure — body and footer
Section titled “Panel structure — body and footer”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>Accessibility
Section titled “Accessibility”- 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.
Theming tokens
Section titled “Theming tokens”| Token path | Purpose |
|---|---|
popover.bg / -fg / -border | Surface colors. |
popover.radius | Border radius. |
popover.padding | Inner padding. |
popover.min-width / -max-width | Size bounds. |
popover.body-gap / -footer-gap | Gaps inside __body / __footer (the body gap also separates the footer from the body). |
CSS variables
Section titled “CSS variables”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-gapRelated
Section titled “Related”- Dialog — modal variant for confirmation flows.
filter-popoverrecipe — popover + form + htmx.
Used in recipes: Filter popover