Hover card
hc-hovercard is the richer-content sibling of
hc-tooltip. Use it
when the popup needs an avatar, a title and subtitle, a paragraph
description, or interactive links — anywhere a short text label is
not enough. Common examples: GitHub-style @user mention previews,
issue ID previews, page link previews.
Built on the same primitives as hc-tooltip and hc-menu: native
popover attribute, CSS Anchor Positioning,
installHovercard() for the open / close routing. The key
behavioural differences from tooltip:
- The card receives pointer events (no
pointer-events: none), so users can move the cursor into it and click links inside. - The behavior tracks hover state on both the trigger and the card — the card stays open while either is hovered.
- Show / hide delays are longer (500 ms / 200 ms) to suit the reading-card UX.
Basic example
Section titled “Basic example”Hover or focus Ada Lovelace for a richer preview.
First computer programmer. View profile →
<a href="/users/ada" aria-describedby="ada-card">Ada Lovelace</a>
<div class="hc-hovercard" id="ada-card"> <header class="hc-hovercard__header"> <span class="hc-avatar" aria-label="Ada Lovelace">AL</span> <div> <div class="hc-hovercard__title">Ada Lovelace</div> <div class="hc-hovercard__subtitle">Mathematician</div> </div> </header> <div class="hc-hovercard__body"> <p>First computer programmer. <a href="/users/ada">View profile →</a></p> </div></div>import { installHovercard } from '@hypermedia-components/core';installHovercard();installHovercard() is idempotent and returns an uninstaller. The
zero-config @hypermedia-components/core/behaviors entry auto-installs it
and picks up content swapped in by htmx.
How installHovercard() routes show / hide
Section titled “How installHovercard() routes show / hide”| Trigger event | Effect |
|---|---|
trigger mouseenter | Schedule show after 500 ms. |
trigger mouseleave | Schedule hide after 200 ms (cancels pending show). |
trigger focus | Show immediately (a11y). |
trigger blur | Schedule hide after 200 ms. |
card mouseenter | Cancel any pending hide. |
card mouseleave | Schedule hide after 200 ms. |
Escape on trigger or card | Close. |
The card stays open while either the trigger or the card is hovered, so the cursor can travel through the small gap between them without dismissing.
Placement
Section titled “Placement”The card opens below the trigger by default. Override it with data-side
(top / right / bottom / left) and optional data-align (start /
center / end, default center); add data-arrow for a pointer.
<a href="/users/grace" aria-describedby="grace-card">Grace Hopper</a><div class="hc-hovercard" id="grace-card" data-side="right" data-arrow>…</div>This shares the directional-placement mechanism with tooltip and popover — CSS Anchor Positioning with a JS fallback. See Fundamentals → Anchored positioning for the shared mechanics and the Popover → Directional placement baseline note.
htmx usage
Section titled “htmx usage”Fetch the card body lazily from the trigger’s own hover / focus:
htmx’s from: modifier listens on the link while the swap targets
the card.
<a href="/users/ada" id="ada-link" aria-describedby="ada-card">Ada Lovelace</a>
<div class="hc-hovercard" id="ada-card" data-hx-get="/users/ada/preview" data-hx-trigger="mouseenter once from:#ada-link, focus once from:#ada-link" data-hx-target="this"> <span class="hc-spinner" aria-hidden="true"></span></div>once makes the first hover (or keyboard focus) fetch the fragment;
the server returns the card’s inner HTML. The
lazy-panel recipe
documents the same load-on-demand contract in full.
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 |
Browsers without anchor support fall back to a
getBoundingClientRect placement that mirrors the default-below +
flip-block CSS path.
Accessibility
Section titled “Accessibility”- The trigger references the card via
aria-describedbyso screen readers announce the card’s text content on trigger focus, regardless of whether it is visually rendered. - Hover-card content is supplementary. Anything necessary to understand the page must live in the document body too.
- Do not put primary actions inside a hover card — a user who never hovers will never see them. Links to “view full profile” are fine; the action they trigger should also be reachable via the trigger element directly (e.g. the trigger is the same link).
Escapealways closes the card without moving focus.
Theming tokens
Section titled “Theming tokens”Component tokens (in component.tokens.json):
| Token path | Purpose |
|---|---|
hovercard.bg / fg / border / radius | Surface. |
hovercard.max-width | Cap before wrapping. |
hovercard.padding / gap | Inner layout. |
hovercard.offset | Distance from trigger when anchored. |
hovercard.title-weight | .hc-hovercard__title font weight. |
hovercard.subtitle-fg / subtitle-size | .hc-hovercard__subtitle color and size. |
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-hovercard-bg | -fg | -border | -radius--hc-hovercard-max-width | -padding | -gap | -offset--hc-hovercard-title-weight--hc-hovercard-subtitle-fg | -subtitle-size