Skip to content
Components

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

Props

  • openDelay
  • closeDelay
  • side
  • align
  • sideOffset
  • alignOffset
  • avoidCollisions
  • name
  • description
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

NameTypeValuesDefaultDescription
openboolean--Open state when controlled.
defaultOpenboolean-falseInitial open state.
onOpenChange(open: boolean) => void--Called when it opens or closes.
openDelaynumber-600Milliseconds before opening.
closeDelaynumber-300Milliseconds before closing.
sideenumtop | right | bottom | leftbottomOn HoverCardContent: preferred side; flips when there is no room.
alignenumstart | center | endcenterOn HoverCardContent: alignment along the trigger.
sideOffsetnumber-4On HoverCardContent: distance from the trigger in px.
asChildboolean-falseOn HoverCardTrigger: apply hover and focus behavior to one real link child. Use true for Link or a.
alignOffsetnumber-0On HoverCardContent: offset along the alignment axis in px.
avoidCollisionsboolean-trueOn HoverCardContent: shift or flip the preview to keep it in the viewport.
collisionPaddingnumber | Partial<Record<"top" | "right" | "bottom" | "left", number>>-8On HoverCardContent: minimum distance from viewport edges in px.

Slots

NameRequiredDescription
triggeryesThe link that opens the card.
contentyesPreview content: Avatar, name, short text, Metadata.

Anatomy

PartDescription
rootHoverCard: holds open state, renders nothing.
triggerHoverCardTrigger: a link, usually Link with asChild.
contentHoverCardContent: read-only floating card, 320px wide by default, on the popover stacking layer.

Accessibility

Role: none

KeysAction
TabFocus on the trigger opens the card after openDelay; leaving focus closes it after closeDelay.
EscapeCloses 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-card

Run 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.