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.
Installation#
Usage#
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>
)
}Anatomy#
import {
HoverCard,
HoverCardTrigger,
HoverCardContent,
HoverCardMedia,
HoverCardTitle,
HoverCardDescription,
HoverCardFooter,
} from "@/components/ui/hover-card"
<HoverCard>
<HoverCardTrigger />
<HoverCardContent>
<HoverCardMedia />
<HoverCardTitle />
<HoverCardDescription />
<HoverCardFooter />
</HoverCardContent>
</HoverCard>Link preview#
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.
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.
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, Radix HoverCard.Root; renders no DOM. Every part that renders an element accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| openDelay | number | 700 | Milliseconds the pointer rests on the trigger before the card opens. |
| closeDelay | number | 300 | Milliseconds after the pointer leaves before the card closes: the time it has to cross into the card. |
| open | boolean | - | Controlled open state. |
| defaultOpen | boolean | false | Initial open state when uncontrolled. |
| onOpenChange | (open: boolean) => void | - | Called when the card opens or closes. |
HoverCardTrigger
The link being previewed, Radix HoverCard.Trigger. Renders an <a> and forwards all its props.
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Merge onto your own link (a Link, a Next.js link) instead of rendering an <a>. |
| Data attribute | Description |
|---|---|
| data-state | "open" or "closed". |
HoverCardContent
The card, Radix HoverCard.Content portalled to the body; forwards all its props. Declares --surface, so a nested control blends with the card.
| Prop | Type | Default | Description |
|---|---|---|---|
| side | "top" | "right" | "bottom" | "left" | "bottom" | Preferred side of the trigger; the card flips when there is no room. |
| align | "start" | "center" | "end" | "center" | Alignment against the trigger. |
| sideOffset | number | 8 | Gap in pixels between the trigger and the card. |
| collisionPadding | number | 8 | Distance in pixels kept from the viewport edge. |
| showArrow | boolean | false | Render a small pointer toward the trigger. |
| density | "comfortable" | "compact" | "compact" | Padding: 16px or 12px. Falls back to the nearest DensityProvider. |
| Data attribute | Description |
|---|---|
| data-state | "open" or "closed". |
| data-side | The side the card actually opened on, after collisions. |
| data-align | "start", "center" or "end". |
| CSS variable | Description |
|---|---|
| --radix-hover-card-content-transform-origin | The trigger-side origin the card grows from. |
| --radix-hover-card-content-available-width | Width left between the trigger and the viewport edge. |
| --radix-hover-card-content-available-height | Height left between the trigger and the viewport edge. |
| --surface | Set to var(--popover), read by nested Inputs and Selects. |
HoverCardTitle
The card's heading. Forwards all native <h4> props.
HoverCardDescription
Muted supporting copy under the title. Forwards all native <p> props.
HoverCardMedia
One <img> or <video>, aspect-video, set 8px in from the card's edges (4px compact) with concentric corners. Put it first or last. Forwards all native <div> props.
HoverCardFooter
A muted flex row inset like the media; always the last part. Forwards all native <div> props.