Hover Card
A popover card that appears on hover to preview additional content without navigating away.
Usage
HoverCard reveals a floating panel when the pointer rests on a trigger, letting you preview rich
content — a profile, a thumbnail, a summary — without a click or a navigation. Hovering opens it
after openDelay; leaving the trigger and the card closes it after closeDelay, so the pointer
can travel from one to the other without it vanishing.
Focus opens the card immediately rather than on a timer, and Escape closes it while it is open. The trigger is a wrapper, not a control: give it a genuinely focusable child — a link or a button — or there is nothing for the keyboard to reach.
Anatomy
Import the HoverCard component and access all parts using dot notation.
import {HoverCard} from "@blakeui/pro-react";
<HoverCard>
<HoverCard.Trigger />
<HoverCard.Content>
<HoverCard.Arrow />
</HoverCard.Content>
</HoverCard>;HoverCard.Arrow is optional; everything else is required. HoverCard.Content wraps a React Aria
Popover and renders into a portal, so it
escapes any overflow clipping between it and the trigger.
Controlled
Pass open with onOpenChange to drive the card yourself, or defaultOpen to set the initial state
and leave the component to manage it.
Custom Delays
openDelay defaults to 700ms and closeDelay to 300ms. Either at 0 — or below — skips the
timer and switches immediately.
Placements
placement sits on HoverCard.Content and defaults to 'top'. It is a preference rather than a
guarantee: React Aria flips the card to the opposite side when the chosen one would run past the
viewport, and data-placement always reports where it actually landed.
With Arrow
With Image
CSS Classes
Element Classes
.hover-card__trigger— The inline wrapper around your trigger element.display: inline-flex, so it takes no layout space of its own..hover-card__content— The floating panel.max-width: var(--container-sm, 24rem),padding: calc(var(--spacing) * 4),border-radius: calc(var(--radius) * 2),background-color: var(--overlay)andoutline-style: none. It keepsbox-shadow: var(--overlay-shadow): this is a genuinely floating surface, so the elevation is doing real work rather than decorating a card..hover-card__arrow— The optional pointer connecting content to trigger.display: block, with itssvgfilledvar(--overlay)so the arrow and the panel read as one shape.
Interactive States
Both state attributes are set by React Aria on .hover-card__content, and both carry the literal
value "true" rather than being bare.
[data-entering="true"]—animation: enter 0.15s var(--ease-out, cubic-bezier(0, 0, 0.2, 1)), from--tw-enter-opacity: 0and--tw-enter-scale: 0.97.[data-exiting="true"]—animation: exit 0.1s var(--ease-out, …), to--tw-exit-opacity: 0and--tw-exit-scale: 0.97. The card stays mounted until this animation ends.- Both add
will-change: opacity, transformfor the duration of the animation, and no longer. [data-placement="top|bottom|left|right"]— On the content, picks the direction the entrance slides from: one--spacingunit along the axis it opened on, so the card always arrives out of its trigger. On the arrow, rotates thesvg—180degatbottom,-90degatleft,90degatright.
Motion
Reduced motion is gentler, never zero: the fade survives at full length and only the movement — the
0.97 scale and the directional slide — is neutralised, so the card still reads as arriving rather
than teleporting. The opt-out is dual and both halves ship: an explicit [data-reduce-motion="true"]
on the card or any ancestor, and @media (prefers-reduced-motion: reduce) scoped so the two never
double-apply.
API Reference
HoverCard
The root. Owns the open state and the open/close timers, and hands them to the parts through context. Renders no DOM of its own.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | A HoverCard.Trigger and a HoverCard.Content. Required. |
open | boolean | — | Controlled open state. Pair it with onOpenChange. |
defaultOpen | boolean | false | Initial open state when uncontrolled. |
onOpenChange | (open: boolean) => void | — | Called whenever the card wants to open or close — hover, focus, Escape, or an interaction outside it. |
openDelay | number | 700 | Milliseconds the pointer must rest on the trigger before the card opens. 0 or less opens immediately. Focus ignores this and opens at once. |
closeDelay | number | 300 | Milliseconds before the card closes once the pointer has left both the trigger and the card. This is the grace period that lets the pointer cross the gap between them. |
HoverCard.Trigger
The hoverable element — a <span> that tracks pointer and focus events on whatever you put inside
it. Accepts every native <span> attribute, including ref; its own handlers compose with yours
rather than replacing them.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The element that opens the card. Make it focusable — a link or a button — so the card is reachable by keyboard. Required. |
className | string | — | Class name for the trigger wrapper. |
HoverCard.Content
The floating panel. Wraps React Aria
Popover and accepts all of its props
except isOpen and triggerRef, which the root supplies.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | (renderProps) => ReactNode | — | The card's content, and optionally a HoverCard.Arrow. A function receives React Aria's popover render props. Required. |
placement | Placement | "top" | Preferred side, flipped automatically when it would overflow the viewport. Read the resolved side off data-placement. |
offset | number | 8 | Distance in pixels between the card and its trigger. |
className | string | (renderProps) => string | — | Class name for the panel. |
HoverCard.Arrow
The optional pointer. Wraps React Aria OverlayArrow and accepts all of its props.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | a 12×12 SVG | A custom arrow. Left empty, the component draws its own triangle and rotates it to match data-placement. |
className | string | (renderProps) => string | — | Class name for the arrow. |