Chuyển tới nội dung
Component

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.

Xem trước

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>

Cách dùng

Khi nào dùng

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

Khi nào không dùng

  • A short label for a control. Use Tooltip.
  • A panel with buttons, fields or other controls. Use Popover.

Biến thể

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>

Ví dụ

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>

Nên và không nên

Nên

  • Keep the content read-only.
  • Grow from the trigger side (origin by side), exit with animate-exit-scale.

Không nên

  • Do not put buttons or forms inside; use Popover.
  • Do not use on buttons; only links.

Props

TênKiểuGiá trịMặc địnhMô tả
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.

Slot

TênBắt buộcMô tả
triggercóThe link that opens the card.
contentcóPreview content: Avatar, name, short text, Metadata.

Cấu tạo

PhầnMô tả
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.

Khả năng truy cập

Role: none

PhímHành động
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.

Cài đặt

npx werewolf-ui add hover-card

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.