Hover Card

A preview that opens while the pointer rests on a link: the profile behind a mention, the page behind a link, the commit behind a hash. Popover's surface, grown out of the link it previews.

The new form controls shipped this morning, designed by @katiep and reviewed by the whole team.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add hover-card

Usage#

usage.tsxTSX
import {  HoverCard,  HoverCardTrigger,  HoverCardContent,} from "@/components/ui/hover-card"export function Example() {  return (    <HoverCard>      <HoverCardTrigger asChild>        <a href="/people/katiep">@katiep</a>      </HoverCardTrigger>      <HoverCardContent>Katie Park, Design Systems</HoverCardContent>    </HoverCard>  )}

The page behind a link. HoverCardMedia sets the picture 8px in from the card's edges instead of bleeding to them, with concentric corners (the card's 16px radius minus 8px of inset leaves 8px on the picture). The copy under it keeps the card's own 16px. It is aspect-video until you give it another ratio.

Everything that changed is in the release notes for 2.4.

Commit preview#

A reference in running text, opened into what it points at: the subject, the author, and the size of the change. The counts sit in HoverCardFooter, a muted well set in from the card's edges like the media, where a divider line would otherwise go. They are tabular-nums, so a column of them lines up. align="start" opens the card from the hash's left edge.

Fixed in 8f3a2c1, released with 2.4.1.

Placement#

side, align and sideOffset are Radix's, and the card flips when the side you asked for has no room. showArrow adds a pointer toward the link. The card grows out of the trigger it previews, from Radix's transform origin, rather than from its own centre. These open after 150ms so you can sweep across them.

Timing#

The card waits openDelay (700ms) before it opens, so a pointer passing over a paragraph full of links doesn't set off a string of cards, and closeDelay (300ms) after the pointer leaves, which is the time it has to cross the gap into the card. Both are props on HoverCard. Shorten the open delay where the preview is the point (a list of people), and keep the close delay: without it the card vanishes on the way to it.

Accessibility#

A hover card is for sighted pointer users. It opens when the trigger has focus, but the card never takes focus and it never opens on touch, so everything in it must also be reachable where the link goes. Make the trigger a real link to that place, and never put the only way to do something inside the card. When the content has to be operated, use a Popover, which opens on a click and moves focus into itself.

API reference#

HoverCard

The root, no DOM. Props: openDelay (700), closeDelay (300), open, defaultOpen, onOpenChange.

HoverCardTrigger

Renders an <a>; pass asChild to use your own link (a Link, a Next.js link).

HoverCardContent

The card, portalled to the body. Props: side, align (center), sideOffset (8), showArrow, density (comfortable 16px padding · compact 12px), className. It declares --surface, so a nested control blends with the card.

HoverCardTitle · HoverCardDescription

Optional type for the card: an <h4> title and a muted <p>, the same pair Popover uses.

HoverCardMedia · HoverCardFooter

The inset parts, Popover's own. HoverCardMedia frames one <img> or <video> (aspect-video, override with className), first or last in the card. HoverCardFooter is a muted flex row that always closes the card. Both sit 8px in from the edges (4px at compact) with a concentric radius, and neither takes a density of its own.

FAQ#

A Tooltip names a control in a few words and never takes the pointer. A Hover Card previews where a link goes, and the pointer can move into it. A Popover opens on a click and takes focus, so it is the one for anything that has to be operated: forms, pickers, confirmations.