Hover card
Radix panel that opens on hover. Richer than a Tooltip: it holds full JSX (avatar, title, description, metadata) instead of a single line of text. Use it to preview a person, an entity, or a definition without making the user click through.
Anatomy
- 1HoverCard
The Root that wires the trigger to the panel and owns the open state and the open / close delays.
- 2HoverCardTrigger
The element that opens the panel on hover. Pass asChild to wrap a link or an Avatar. Style it so it reads as hoverable (primary + underline on hover, or a dotted underline for a defined term).
- 3HoverCardContent
The floating panel: 320px wide, 8px radius, 1px neutral border, shadow-md, 16px padding. Holds any JSX.
States
The panel itself has just two states: closed and open (it fades in via animate-dialog-in). The visible state lives on the trigger, which should signal it is hoverable, then reinforce it on hover (underline).
<HoverCard>
<HoverCardTrigger className="font-medium text-primary underline-offset-4 hover:underline">
@nextjs
</HoverCardTrigger>
<HoverCardContent>…</HoverCardContent>
</HoverCard>Recipes
Ready-to-copy compositions covering the most common Yuno usages of this atom.
The canonical Figma layout: 48px avatar next to a semibold handle, one line of description, and a muted metadata row (joined date). Trigger it from an @mention or a name.
<HoverCard>
<HoverCardTrigger className="font-medium text-primary underline-offset-4 hover:underline">
@nextjs
</HoverCardTrigger>
<HoverCardContent align="start">
<div className="flex gap-4">
<Avatar className="size-12"><AvatarFallback>N</AvatarFallback></Avatar>
<div className="flex flex-col gap-1">
<p className="text-sm font-semibold text-foreground">@nextjs</p>
<p className="text-sm text-foreground">
The React Framework, created and maintained by @vercel.
</p>
<div className="flex items-center gap-2 pt-2 text-xs text-muted-foreground">
<CalendarBlank weight="light" className="size-4" />
Joined December 2024
</div>
</div>
</div>
</HoverCardContent>
</HoverCard>Preview a record straight from a table cell (a transaction, a customer, a rule) so the user reads the key facts without opening the detail view. Pair the id with a status Badge.
<HoverCard>
<HoverCardTrigger className="font-medium text-primary underline-offset-4 hover:underline">
txn_8f2c…a91
</HoverCardTrigger>
<HoverCardContent align="start">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between gap-4">
<span className="text-sm font-semibold text-foreground">txn_8f2c…a91</span>
<Badge variant="secondary">Succeeded</Badge>
</div>
<p className="text-sm text-muted-foreground">Adyen · Visa ···· 4242</p>
<div className="flex items-center gap-2 pt-2 text-xs text-muted-foreground">
<CreditCard weight="light" className="size-4" />
USD 129.00 · Jul 21, 2026
</div>
</div>
</HoverCardContent>
</HoverCard>Explain domain jargon inline (3DS challenge, chargeback, settlement). Give the term a dotted underline and drop openDelay so the definition surfaces quickly.
<HoverCard openDelay={200}>
<HoverCardTrigger className="underline decoration-dotted underline-offset-4">
3DS challenge
</HoverCardTrigger>
<HoverCardContent align="start" className="w-72">
<p className="text-sm font-semibold text-foreground">3DS challenge</p>
<p className="pt-1 text-sm text-muted-foreground">
An extra step where the issuer asks the shopper to confirm their identity
before the payment is authorized.
</p>
</HoverCardContent>
</HoverCard>Import
Copy this import line at the top of the file where you compose this atom.
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";Props
Everything else from the underlying HTML or Radix primitive is forwarded via ...props.
| Prop | Type | Default | Description |
|---|---|---|---|
| HoverCard.open / defaultOpen | boolean | — | Controlled or uncontrolled open state. Controlled needs onOpenChange. |
| HoverCard.onOpenChange | (open: boolean) => void | — | Fires whenever the panel opens or closes. |
| HoverCard.openDelay | number | 700 | Ms the pointer must rest on the trigger before the panel opens. Lower it (150–300) for a snappier feel. |
| HoverCard.closeDelay | number | 300 | Ms before the panel closes after the pointer leaves. The grace window that lets the pointer travel onto the panel. |
| HoverCardTrigger.asChild | boolean | false | Merges props onto the child element (Radix Slot). Pass when wrapping a real element like a link or an Avatar. |
| HoverCardContent.align | "start" | "center" | "end" | "center" | Alignment along the side. Anchored triggers (a handle in a sentence) usually read best with align='start'. |
| HoverCardContent.side | "top" | "right" | "bottom" | "left" | "bottom" | Preferred side. Radix auto-flips if there's no room. |
| HoverCardContent.sideOffset | number | 4 | Distance in px between trigger and content. |
| HoverCardContent.className | string | — | Merged. Default is w-80 (320px), rounded-md border, shadow-md, p-4. Override w-* for a narrower or wider panel. |
When to use
- Previewing a person on hover of an @mention or an avatar.
- Previewing a record (transaction, customer, rule) from a table cell without leaving the table.
- Explaining domain jargon inline with a short definition.
When not to use
- For a single line of plain-text help, use Tooltip.
- When the content is interactive or must survive a click, use Popover or Dialog.
- On touch, there is no hover, so never hide anything critical behind it.
Usage
- Anchor to a real, visibly hoverable trigger (primary + underline, or a dotted underline for a term).
- Keep the panel to the 320px default width and a few lines of content.
- Lower openDelay (150–300ms) for entity or term previews so it feels responsive.
- Don't hide critical information behind hover, it is invisible on touch and to keyboard users.
- Don't nest multi-step interactive controls, the panel closes as soon as the pointer drifts off.
- Don't use it where a single line of text would do, that is a Tooltip.
Related
Cross-links to atoms and patterns you may reach for next.