Chuyển tới nội dung

Pro đang mở bán trước. Cài đặt mở từ tháng 1/2027. Xem Pro

Component

HoverCard

Card xem trước mở khi hover hoặc focus bàn phím trên một link (một người dùng, một repository, một mục) và hiện thêm về đích. Người dùng chuột nhìn thấy màn hình nhận được bản xem trước; link vẫn hoạt động khi đứng một mình. Dùng Tooltip cho nhãn ngắn, Popover cho nội dung mở bằng click và chứa control.

  • npm: 0 trực tiếp, 2 tổng
  • registry: 3 trực tiếp, 3 tổng

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">Nhà thiết kế sản phẩm đang xây dựng những công cụ chu đáo.</p>
      </div>
    </div>
  </HoverCardContent>
</HoverCard>

Cách dùng

Khi nào dùng

  • Hiện chi tiết chỉ đọc về một người hoặc một mục được link khi người dùng chuột hover hoặc người dùng bàn phím focus vào đó.
  • Giữ link hữu ích trên cảm ứng, nơi một lần chạm đi tới đích mà không mở bản xem trước.

Khi nào không dùng

  • Nhãn ngắn cho một control. Dùng Tooltip.
  • Một panel có button, field hoặc control khác. Dùng Popover.

Biến thể

side

Trên HoverCardContent: cạnh ưu tiên; lật khi không đủ chỗ.

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">Nhà thiết kế sản phẩm đang xây dựng những công cụ chu đáo.</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">Nhà thiết kế sản phẩm đang xây dựng những công cụ chu đáo.</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">Nhà thiết kế sản phẩm đang xây dựng những công cụ chu đáo.</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">Nhà thiết kế sản phẩm đang xây dựng những công cụ chu đáo.</p>
      </div>
    </div>
  </HoverCardContent>
</HoverCard>

align

Trên HoverCardContent: căn dọc theo 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">Nhà thiết kế sản phẩm đang xây dựng những công cụ chu đáo.</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">Nhà thiết kế sản phẩm đang xây dựng những công cụ chu đáo.</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">Nhà thiết kế sản phẩm đang xây dựng những công cụ chu đáo.</p>
      </div>
    </div>
  </HoverCardContent>
</HoverCard>

Ví dụ

Xem trước người dùng

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">Nhà thiết kế sản phẩm</p>
      </div>
    </div>
  </HoverCardContent>
</HoverCard>

Xem trước dự án

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">Không gian làm việc</Link>
  </HoverCardTrigger>
  <HoverCardContent side="right" align="start">
    <p className="text-sm font-medium">Không gian làm việc</p>
    <p className="text-sm text-text-secondary">Tệp được chia sẻ và ghi chú dự án cho nhóm của bạn.</p>
    <Badge>Riêng tư</Badge>
  </HoverCardContent>
</HoverCard>

Độ trễ ngắn

Card mở sau 200ms và đóng sau 100ms. Một lần chạm trên cảm ứng vẫn đi theo 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">Báo cáo quý</Link>
  </HoverCardTrigger>
  <HoverCardContent side="top" align="end" sideOffset={8}>
    <p className="text-sm font-medium">Báo cáo quý</p>
    <p className="text-sm text-text-secondary">Xem xét các cập nhật dự án mới nhất trước khi mở tài liệu.</p>
  </HoverCardContent>
</HoverCard>

Nên và không nên

Nên

  • Giữ nội dung chỉ đọc.
  • Nở từ phía trigger (origin theo cạnh), thoát bằng animate-exit-scale.

Không nên

  • Đừng đặt button hoặc form bên trong; dùng Popover.
  • Đừng dùng trên button; chỉ dùng cho link.

Props

TênKiểuGiá trịMặc địnhMô tả
openboolean--Trạng thái mở khi controlled.
defaultOpenboolean-falseTrạng thái mở ban đầu.
onOpenChange(open: boolean) => void--Được gọi khi mở hoặc đóng.
openDelaynumber-600Số mili giây trước khi mở.
closeDelaynumber-300Số mili giây trước khi đóng.
sideenumtop | right | bottom | leftbottomTrên HoverCardContent: cạnh ưu tiên; lật khi không đủ chỗ.
alignenumstart | center | endcenterTrên HoverCardContent: căn dọc theo trigger.
sideOffsetnumber-4Trên HoverCardContent: khoảng cách từ trigger, tính bằng px.
asChildboolean-falseTrên HoverCardTrigger: gắn hành vi hover và focus lên một phần tử con là link thật. Dùng true cho Link hoặc a.
alignOffsetnumber-0Trên HoverCardContent: độ lệch dọc theo trục căn, tính bằng px.
avoidCollisionsboolean-trueTrên HoverCardContent: dịch hoặc lật bản xem trước để giữ trong viewport.
collisionPaddingnumber | Partial<Record<"top" | "right" | "bottom" | "left", number>>-8Trên HoverCardContent: khoảng cách tối thiểu tới mép viewport, tính bằng px.

Slot

TênBắt buộcMô tả
triggercóLink mở card.
contentcóNội dung xem trước: Avatar, tên, văn bản ngắn, Metadata.

Cấu tạo

PhầnMô tả
rootHoverCard: giữ trạng thái mở, không render gì.
triggerHoverCardTrigger: một link, thường là Link với asChild.
contentHoverCardContent: card nổi chỉ đọc, rộng 320px theo mặc định, trên lớp xếp chồng popover.

Khả năng truy cập

Role: none

PhímHành động
TabFocus trên trigger mở card sau openDelay; rời focus thì đóng sau closeDelay.
EscapeĐóng card; focus giữ trên trigger.
  • Nội dung mang tính bổ sung: không nằm trong thứ tự tab và không được là đường dẫn duy nhất tới một hành động.
  • Trigger là một link thật; card không đổi tên của link. Đặt thông tin thiết yếu trong văn bản của link.
  • Trên cảm ứng không có hover: một lần chạm đi theo link và card không mở.
  • Card giữ mở khi con trỏ di từ trigger vào trong card.

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.