Hover card

span.hover-card[popover="hint"][data-ui="hover-card"]

Hover card — a rich preview shown on hover, focus or long-press.

Examples

A preview behind a link

View “A preview behind a link” as

Work by @ada Ada Lovelace Wrote the first algorithm intended for a machine. View profile on the analytical engine.

<p>
  Work by
  <a class="link" id="demo-hc-trigger" href="#demo-hc" interestfor="demo-hc">@ada</a>
  <span class="hover-card" id="demo-hc" popover="hint" data-ui="hover-card">
    <span class="hover-card-header">
      <img class="hover-card-media" src="/demo/portrait.svg" alt=""
           width="200" height="200" />
      <span>
        <span class="hover-card-title">Ada Lovelace</span>
        <span class="hover-card-text">Wrote the first algorithm intended for a machine.</span>
      </span>
    </span>
    <span class="hover-card-actions">
      <a class="btn btn-sm btn-outline" href="#demo-hc">View profile</a>
    </span>
  </span>
  on the analytical engine.
</p>

The panel sits immediately after its trigger in the DOM, and that is a requirement rather than tidiness: a hover card is reached by Tabbing from the trigger into the card, and Tab follows DOM order — a card defined at the end of the document would be reachable only after every other control on the page.

Focus moving into the panel keeps it open, which is what makes it usable by keyboard at all. Because the panel holds a link, the trigger is associated with it by aria-details rather than aria-describedby — the browser makes that switch natively, and applyInterestForAria() makes the same one on the fallback path. Write neither by hand.

Tokens 20

Level 2, declared on .hover-card itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by hover-card
TokenDefault
--hover-card-durationvar(--duration-fast)
--hover-card-media-radiusvar(--radius-full)
--hover-card-media-size3rem
--hover-card-offsetvar(--space-2)
--hover-card-text-colorvar(--color-on-surface-muted)
--hover-card-text-font-sizevar(--font-size-xs)
--hover-card-title-font-familyvar(--font-primary)
--hover-card-title-font-sizevar(--font-size-sm)
--hover-card-title-font-weightvar(--font-weight-semibold)
--hover-card-areablock-end span-all
--hover-card-tryflip-block, flip-inline, flip-block flip-inline
--hover-card-bgvar(--color-surface-raised)
--hover-card-border-colorvar(--color-border)
--hover-card-border-widthvar(--border-width)
--hover-card-colorvar(--color-on-surface)
--hover-card-gapvar(--space-2)
--hover-card-inline-size20rem
--hover-card-paddingvar(--space-4)
--hover-card-radiusvar(--radius-box)
--hover-card-shadowvar(--elevation-overlay)

Variants and states

Variants

  • .hover-card-block-start
  • .hover-card-lg
  • .hover-card-sm

Inside it

  • .hover-card-header
  • .hover-card-media
  • .hover-card-actions
  • .hover-card-text
  • .hover-card-title

State it reads

Read from the platform, never mirrored into a class that could disagree with it.

  • :popover-open
  • data-legacy

Before you ship it

This one needs a script. Opt the markup in with data-ui="hover-card" and import import 'mostlycss/js/components/hover-card' — that specifier and not the package root, which is a bundle carrying a second copy of the registry, so a page holding both initialises every element twice. What you lose without the script is listed below — for some components that is only polish, for others it is the whole interaction.

What you have to do 4 requirements

  1. Put the panel immediately after its trigger in the DOM. A card is reached by Tabbing out of the trigger and into it, and Tab follows DOM order — a card defined at the end of the document is reachable only after every other control on the page.
  2. Ship data-ui="hover-card". Two of the three engines do not open it at all without the module.
  3. Write neither aria-describedby nor aria-details. The association is supplied for you, and aria-describedby by hand flattens a card containing a link into a string a screen reader reads out and then cannot enter.
  4. Trigger it from a <button> or an <a>, the two elements interestfor is defined on.

src/css/components/hover-card.css · npx mostlycss add hover-card