HoverCard
A preview card that opens on hover or keyboard focus of a link (a user, a repository, an item) and shows more about the target. Sighted mouse users get a preview; the link still works alone. Use Tooltip for a short label, Popover for content opened by a click that holds controls.
Preview
import { Avatar } from "@/components/ui/avatar"
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
import { Link } from "@/components/ui/link"
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#profile">Khanh Nguyen</Link>
</HoverCardTrigger>
<HoverCardContent>
<div className="flex items-start gap-md">
<Avatar alt="" size="lg">KN</Avatar>
<div className="flex flex-col gap-xs">
<p className="text-sm font-medium">Khanh Nguyen</p>
<p className="text-sm text-text-secondary">Product designer building thoughtful tools.</p>
</div>
</div>
</HoverCardContent>
</HoverCard>Usage
When to use
- Show read-only details about a linked person or item when a mouse user hovers or a keyboard user focuses it.
- Keep the link useful on touch, where tapping follows the destination without opening the preview.
When not to use
- A short label for a control. Use Tooltip.
- A panel with buttons, fields or other controls. Use Popover.
Variants
side
On HoverCardContent: preferred side; flips when there is no room.
import { Avatar } from "@/components/ui/avatar"
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
import { Link } from "@/components/ui/link"
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#profile">Khanh Nguyen</Link>
</HoverCardTrigger>
<HoverCardContent side="top">
<div className="flex items-start gap-md">
<Avatar alt="" size="lg">KN</Avatar>
<div className="flex flex-col gap-xs">
<p className="text-sm font-medium">Khanh Nguyen</p>
<p className="text-sm text-text-secondary">Product designer building thoughtful tools.</p>
</div>
</div>
</HoverCardContent>
</HoverCard>
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#profile">Khanh Nguyen</Link>
</HoverCardTrigger>
<HoverCardContent side="right">
<div className="flex items-start gap-md">
<Avatar alt="" size="lg">KN</Avatar>
<div className="flex flex-col gap-xs">
<p className="text-sm font-medium">Khanh Nguyen</p>
<p className="text-sm text-text-secondary">Product designer building thoughtful tools.</p>
</div>
</div>
</HoverCardContent>
</HoverCard>
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#profile">Khanh Nguyen</Link>
</HoverCardTrigger>
<HoverCardContent>
<div className="flex items-start gap-md">
<Avatar alt="" size="lg">KN</Avatar>
<div className="flex flex-col gap-xs">
<p className="text-sm font-medium">Khanh Nguyen</p>
<p className="text-sm text-text-secondary">Product designer building thoughtful tools.</p>
</div>
</div>
</HoverCardContent>
</HoverCard>
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#profile">Khanh Nguyen</Link>
</HoverCardTrigger>
<HoverCardContent side="left">
<div className="flex items-start gap-md">
<Avatar alt="" size="lg">KN</Avatar>
<div className="flex flex-col gap-xs">
<p className="text-sm font-medium">Khanh Nguyen</p>
<p className="text-sm text-text-secondary">Product designer building thoughtful tools.</p>
</div>
</div>
</HoverCardContent>
</HoverCard>align
On HoverCardContent: alignment along the trigger.
import { Avatar } from "@/components/ui/avatar"
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
import { Link } from "@/components/ui/link"
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#profile">Khanh Nguyen</Link>
</HoverCardTrigger>
<HoverCardContent align="start">
<div className="flex items-start gap-md">
<Avatar alt="" size="lg">KN</Avatar>
<div className="flex flex-col gap-xs">
<p className="text-sm font-medium">Khanh Nguyen</p>
<p className="text-sm text-text-secondary">Product designer building thoughtful tools.</p>
</div>
</div>
</HoverCardContent>
</HoverCard>
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#profile">Khanh Nguyen</Link>
</HoverCardTrigger>
<HoverCardContent>
<div className="flex items-start gap-md">
<Avatar alt="" size="lg">KN</Avatar>
<div className="flex flex-col gap-xs">
<p className="text-sm font-medium">Khanh Nguyen</p>
<p className="text-sm text-text-secondary">Product designer building thoughtful tools.</p>
</div>
</div>
</HoverCardContent>
</HoverCard>
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#profile">Khanh Nguyen</Link>
</HoverCardTrigger>
<HoverCardContent align="end">
<div className="flex items-start gap-md">
<Avatar alt="" size="lg">KN</Avatar>
<div className="flex flex-col gap-xs">
<p className="text-sm font-medium">Khanh Nguyen</p>
<p className="text-sm text-text-secondary">Product designer building thoughtful tools.</p>
</div>
</div>
</HoverCardContent>
</HoverCard>Examples
User preview
import { Avatar } from "@/components/ui/avatar"
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
import { Link } from "@/components/ui/link"
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#khanh">Khanh Nguyen</Link>
</HoverCardTrigger>
<HoverCardContent>
<div className="flex items-start gap-md">
<Avatar alt="" size="lg">KN</Avatar>
<div className="flex flex-col gap-xs">
<p className="text-sm font-medium">Khanh Nguyen</p>
<p className="text-sm text-text-secondary">Product designer</p>
</div>
</div>
</HoverCardContent>
</HoverCard>Project preview
import { Badge } from "@/components/ui/badge"
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
import { Link } from "@/components/ui/link"
<HoverCard>
<HoverCardTrigger asChild>
<Link href="#workspace">Workspace</Link>
</HoverCardTrigger>
<HoverCardContent side="right" align="start">
<p className="text-sm font-medium">Workspace</p>
<p className="text-sm text-text-secondary">Shared files and project notes for your team.</p>
<Badge>Private</Badge>
</HoverCardContent>
</HoverCard>Short delay
The card opens after 200ms and closes after 100ms. A touch tap still follows the link.
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
import { Link } from "@/components/ui/link"
<HoverCard openDelay={200} closeDelay={100}>
<HoverCardTrigger asChild>
<Link href="#report">Quarterly report</Link>
</HoverCardTrigger>
<HoverCardContent side="top" align="end" sideOffset={8}>
<p className="text-sm font-medium">Quarterly report</p>
<p className="text-sm text-text-secondary">Review the latest project updates before opening the document.</p>
</HoverCardContent>
</HoverCard>Do and don't
Do
- Keep the content read-only.
- Grow from the trigger side (origin by side), exit with animate-exit-scale.
Don't
- Do not put buttons or forms inside; use Popover.
- Do not use on buttons; only links.
Props
| Name | Type | Values | Default | Description |
|---|---|---|---|---|
| open | boolean | - | - | Open state when controlled. |
| defaultOpen | boolean | - | false | Initial open state. |
| onOpenChange | (open: boolean) => void | - | - | Called when it opens or closes. |
| openDelay | number | - | 600 | Milliseconds before opening. |
| closeDelay | number | - | 300 | Milliseconds before closing. |
| side | enum | top | right | bottom | left | bottom | On HoverCardContent: preferred side; flips when there is no room. |
| align | enum | start | center | end | center | On HoverCardContent: alignment along the trigger. |
| sideOffset | number | - | 4 | On HoverCardContent: distance from the trigger in px. |
| asChild | boolean | - | false | On HoverCardTrigger: apply hover and focus behavior to one real link child. Use true for Link or a. |
| alignOffset | number | - | 0 | On HoverCardContent: offset along the alignment axis in px. |
| avoidCollisions | boolean | - | true | On HoverCardContent: shift or flip the preview to keep it in the viewport. |
| collisionPadding | number | Partial<Record<"top" | "right" | "bottom" | "left", number>> | - | 8 | On HoverCardContent: minimum distance from viewport edges in px. |
Slots
| Name | Required | Description |
|---|---|---|
| trigger | yes | The link that opens the card. |
| content | yes | Preview content: Avatar, name, short text, Metadata. |
Anatomy
| Part | Description |
|---|---|
| root | HoverCard: holds open state, renders nothing. |
| trigger | HoverCardTrigger: a link, usually Link with asChild. |
| content | HoverCardContent: read-only floating card, 320px wide by default, on the popover stacking layer. |
Accessibility
Role: none
| Keys | Action |
|---|---|
| Tab | Focus on the trigger opens the card after openDelay; leaving focus closes it after closeDelay. |
| Escape | Closes the card; focus stays on the trigger. |
- The content is supplementary: it is not in the tab order and must not hold the only path to an action.
- The trigger is a real link; the card does not change its name. Put the essential info in the link text.
- On touch there is no hover: a tap follows the link and the card does not open.
- The card stays open while the pointer moves from the trigger into it.
Install
npx werewolf-ui add hover-cardRun npx werewolf-ui init once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See Installation for requirements and the alternative registry setup.