Hover Card
A preview of what's behind a link, such as a person, a repository or a page, shown when the link is hovered or focused.
Reviewed by @grace 2 hours ago
import { CalendarDaysIcon, MapPinIcon } from "lucide-react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
export default function HoverCardDemo() {
return (
<p className="text-sm text-muted-foreground">
Reviewed by{" "}
<HoverCard>
<HoverCardTrigger
href="#"
className="font-medium text-foreground underline decoration-current/30 underline-offset-4 hover:decoration-current"
>
@grace
</HoverCardTrigger>
<HoverCardContent className="w-80">
<div className="flex gap-4">
<Avatar size="lg">
<AvatarFallback>GH</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-1 flex-col gap-2">
<div className="flex items-start justify-between gap-2">
<div className="flex flex-col">
<span className="text-sm font-semibold text-foreground">
Grace Hopper
</span>
<span className="text-xs text-muted-foreground">@grace</span>
</div>
<Button size="xs" variant="outline">
Follow
</Button>
</div>
<p className="text-sm leading-5 text-foreground">
Compiler engineer. Design systems, developer tools and very
small nanoseconds.
</p>
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<MapPinIcon className="size-3.5" />
Arlington, VA
</span>
<span className="flex items-center gap-1">
<CalendarDaysIcon className="size-3.5" />
Joined March 2019
</span>
</div>
</div>
</div>
</HoverCardContent>
</HoverCard>{" "}
2 hours ago
</p>
)
}Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/hover-card.jsonUsage
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"<HoverCard>
<HoverCardTrigger href="/people/grace">@grace</HoverCardTrigger>
<HoverCardContent>{/* profile preview */}</HoverCardContent>
</HoverCard>HoverCardTrigger renders a real <a>: the preview is an enhancement of a link that works on its own. It's built on the Base UI Preview Card.
Examples
Issue preview
References to issues, pull requests and documents are good candidates: the card answers what is this and is it done? without a click. Keep it to identity, status, and one or two lines of context.
Fixed in DAT-1284, shipped in v1.4.2.
import { CircleDotIcon, MessageSquareIcon } from "lucide-react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Badge, BadgeDot } from "@/components/ui/badge"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
export default function HoverCardIssuePreview() {
return (
<p className="max-w-md text-center text-sm leading-6 text-muted-foreground">
Fixed in{" "}
<HoverCard>
<HoverCardTrigger
href="#"
className="font-mono font-medium text-link underline decoration-current/30 underline-offset-4 hover:decoration-current"
>
DAT-1284
</HoverCardTrigger>
<HoverCardContent className="flex w-80 flex-col gap-3">
<div className="flex items-center justify-between gap-2">
<span className="flex items-center gap-1.5 font-mono text-xs text-muted-foreground">
<CircleDotIcon className="size-3.5 text-success-11" />
DAT-1284
</span>
<Badge variant="success">
<BadgeDot />
Done
</Badge>
</div>
<p className="text-sm leading-5 font-medium text-foreground">
Date picker traps focus when opened from a popover
</p>
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span className="flex items-center gap-2">
<Avatar size="xs">
<AvatarFallback>LT</AvatarFallback>
</Avatar>
Linus Torvalds · closed 2 days ago
</span>
<span className="flex items-center gap-1 tabular-nums">
<MessageSquareIcon className="size-3.5" />
14
</span>
</div>
</HoverCardContent>
</HoverCard>
, shipped in v1.4.2.
</p>
)
}Link preview
Preview where an external link leads before people commit to leaving the page. Here the card opens above the link (side="top") and uses a tinted footer for metadata.
prfct is built on mui/base-ui, the unstyled primitives from the teams behind Radix, Floating UI and Material UI.
import { GitForkIcon, StarIcon } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
export default function HoverCardLinkPreview() {
return (
<p className="max-w-md text-center text-sm leading-6 text-muted-foreground">
prfct is built on{" "}
<HoverCard>
<HoverCardTrigger
href="https://github.com/mui/base-ui"
target="_blank"
rel="noreferrer"
className="font-medium text-link underline decoration-current/30 underline-offset-4 hover:decoration-current"
>
mui/base-ui
</HoverCardTrigger>
<HoverCardContent side="top" className="w-80 p-0">
<div className="flex flex-col gap-2 p-4">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-foreground">
mui/base-ui
</span>
<Badge variant="outline" size="sm">
Public
</Badge>
</div>
<p className="text-sm leading-5 text-muted-foreground">
Unstyled UI components for building accessible web apps and design
systems.
</p>
</div>
<div className="flex items-center gap-4 border-t bg-surface px-4 py-2.5 text-xs text-muted-foreground">
<span className="flex items-center gap-1.5">
<span className="size-2.5 rounded-full bg-blue-9" />
TypeScript
</span>
<span className="flex items-center gap-1 tabular-nums">
<StarIcon className="size-3.5" />
7.9k
</span>
<span className="flex items-center gap-1 tabular-nums">
<GitForkIcon className="size-3.5" />
410
</span>
</div>
</HoverCardContent>
</HoverCard>
, the unstyled primitives from the teams behind Radix, Floating UI and
Material UI.
</p>
)
}Delay
delay (600ms by default) and closeDelay (300ms) on the trigger decide how intentional a hover must be. Long delays prevent cards from flashing as the pointer crosses a page; short ones suit dense lists that people scan on purpose.
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
const people = [
{
name: "Ada Lovelace",
initials: "AL",
role: "Analytical engines",
delay: 150,
},
{ name: "Alan Turing", initials: "AT", role: "Computability", delay: 600 },
{
name: "Katherine Johnson",
initials: "KJ",
role: "Orbital mechanics",
delay: 1000,
},
]
export default function HoverCardDelay() {
return (
<div className="flex flex-wrap items-center justify-center gap-4">
{people.map((person) => (
<HoverCard key={person.name}>
<HoverCardTrigger
href="#"
delay={person.delay}
closeDelay={150}
className="flex flex-col items-center gap-2 rounded-lg p-2 text-sm text-foreground focus-ring hover:bg-accent"
>
<Avatar size="lg">
<AvatarFallback>{person.initials}</AvatarFallback>
</Avatar>
<span className="font-mono text-xs text-muted-foreground">
{person.delay}ms
</span>
</HoverCardTrigger>
<HoverCardContent className="w-60">
<p className="text-sm font-semibold text-foreground">
{person.name}
</p>
<p className="text-sm text-muted-foreground">{person.role}</p>
</HoverCardContent>
</HoverCard>
))}
</div>
)
}Guidelines
When to use
- To preview the destination of a link: a person, an issue, a repository, a document.
- When the preview helps people decide whether to navigate, without making them navigate.
When not to use
- For essential information — hover cards are invisible on touch devices and easy to miss. Put it on the page.
- For interactive tasks — use a Popover, which opens on click and can hold forms.
- For a one-line label — use a Tooltip.
A preview, not a page
The card should confirm this is what you're looking for. If you find yourself adding tabs, lists or several actions, link to a page instead.
Accessibility
A hover card is a visual enhancement for sighted pointer and keyboard users. It opens when the link is hovered and when it receives keyboard focus, but it isn't announced and the link doesn't reference it, so screen reader users experience the link on its own. Treat the card as optional context.
| Key | Behavior |
|---|---|
Tab | Focusing the link opens the card after the delay. |
Enter | Follows the link. |
Esc | Closes the card. |
- The link must stand on its own. Its text should say where it goes; the card only adds context.
- Don't hide essentials in it. Anything people must know or do can't live only in a hover card.
- Hoverable. Moving the pointer from the link onto the card keeps it open, within the
closeDelay.
API reference
HoverCard
The root. Doesn't render an element. Accepts every prop of Base UI PreviewCard.Root.
HoverCardTrigger
The link that opens the card. Renders an <a> and accepts every anchor attribute.
HoverCardContent
Renders the portal, positioner and popup.