HoverCard
A preview card shown when the pointer rests on a link — a profile, a product, link details.
Kutubxona muallifi — @akhatmirzo.
'use client';
import { Avatar, Badge, HoverCard } from '@akhatmirzo/ui';
import { CalendarDays } from 'lucide-react';
export default function HoverCardProfile() {
return (
<p className="m-0 text-sm">
Kutubxona muallifi —{' '}
<HoverCard>
<HoverCard.Trigger asChild>
<a
href="https://github.com/Akhatmirzo"
className="focus-ring rounded-sm font-medium text-primary underline-offset-4 hover:underline"
>
@akhatmirzo
</a>
</HoverCard.Trigger>
<HoverCard.Content align="start">
<div className="flex gap-3">
<Avatar name="Akhatmirzo Umarov" />
<div className="flex min-w-0 flex-col gap-1">
<span className="font-semibold">Akhatmirzo Umarov</span>
<span className="text-text-muted">
Frontend dasturchi. React, TypeScript va o'zbekcha UI.
</span>
<span className="mt-1 flex items-center gap-2 text-xs text-text-muted">
<CalendarDays className="size-3.5" aria-hidden="true" />
2021-yildan beri
<Badge size="sm" color="success">
Ochiq
</Badge>
</span>
</div>
</div>
</HoverCard.Content>
</HoverCard>
.
</p>
);
}
Import
import { HoverCard } from '@akhatmirzo/ui';When to use
- Preview what's behind a link — a user profile, a product, a document. Users get a quick "what is this?" answer without leaving the page.
- Supplementary content only. The card doesn't open on touch screens and screen readers may not read it — the trigger should be a real link, and the same information should live on the page it points to.
- A short text hint? Use a
Tooltip. - Buttons or a form inside? Use a click-to-open
Popover.
Anatomy
| Part | What it does |
|---|---|
HoverCard | State and delays: openDelay, closeDelay, open |
HoverCard.Trigger | The link (an <a> by default; asChild for your own element) |
HoverCard.Content | The panel: same look as Popover, portal, positioning, size |
<HoverCard openDelay={400}>
<HoverCard.Trigger asChild>
<a href="/u/akhatmirzo">@akhatmirzo</a>
</HoverCard.Trigger>
<HoverCard.Content side="bottom" align="start">
…profile…
</HoverCard.Content>
</HoverCard>The card opens 500ms after the pointer arrives and closes 200ms after it leaves — it doesn't pop up when the pointer just passes by, and there's time to move onto the card. It also opens when the trigger receives keyboard focus.
Sizes
size is sm (240px) or md (320px, the default). Pass a className for any other width.
'use client';
import { HoverCard } from '@akhatmirzo/ui';
export default function HoverCardSizes() {
return (
<div className="flex flex-wrap items-center gap-6 text-sm">
{(['sm', 'md'] as const).map((size) => (
<HoverCard key={size}>
<HoverCard.Trigger asChild>
<a
href="#chorsu"
className="focus-ring rounded-sm font-medium text-primary underline underline-offset-4"
>
size="{size}"
</a>
</HoverCard.Trigger>
<HoverCard.Content size={size}>
<p className="m-0 font-semibold">Chorsu Market</p>
<p className="m-0 mt-1 text-text-muted">
Toshkentdagi onlayn do'kon: 12 000+ mahsulot, 1 kunda yetkazib berish.
</p>
</HoverCard.Content>
</HoverCard>
))}
</div>
);
}
Placement
side is top, right, bottom (default) or left; align is start, center (default) or
end. The panel sits 6px from the trigger and flips to the opposite side near the screen edge.
'use client';
import { Button, HoverCard } from '@akhatmirzo/ui';
const sides = ['top', 'right', 'bottom', 'left'] as const;
export default function HoverCardPlacement() {
return (
<div className="flex flex-wrap items-center gap-3">
{sides.map((side) => (
<HoverCard key={side} openDelay={200} closeDelay={100}>
<HoverCard.Trigger asChild>
<Button variant="outline">{side}</Button>
</HoverCard.Trigger>
<HoverCard.Content side={side} size="sm">
<p className="m-0">
Panel <b>{side}</b> tomonda. Ekran chetiga yetsa, o'zi teskari tomonga o'tadi.
</p>
</HoverCard.Content>
</HoverCard>
))}
</div>
);
}
Don't hide important information
Phones and tablets have no hover — HoverCard never opens there. Keep key information such as prices, statuses or errors visible on the page itself.
Props
HoverCard
| Prop | Type | Default | Description |
|---|---|---|---|
openDelay | number | 500 | Delay before opening after the pointer arrives, ms |
closeDelay | number | 200 | Delay before closing after the pointer leaves, ms |
open | boolean | — | Whether it is open (controlled) |
defaultOpen | boolean | — | Open initially |
onOpenChange | (open: boolean) => void | — | Called on open/close |
HoverCard.Content
Also accepts the rest of the Radix HoverCard.Content props (alignOffset, avoidCollisions
...), <div> attributes and ref.
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'md' | Width: 240 / 320px |
side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | Preferred side |
align | 'start' | 'center' | 'end' | 'center' | Alignment along the trigger |
sideOffset | number | 6 | Gap to the trigger, px |
collisionPadding | number | 8 | Minimum distance from the screen edge, px |
container | HTMLElement | null | — | Portal container (defaults to document.body) |
Data attributes
| Attribute | Where | Value |
|---|---|---|
data-state | trigger, panel | open · closed |
data-side | panel | top · right · bottom · left |
data-size | panel | sm · md |
Accessibility
- Supplementary only. The card is not announced and focus doesn't move into it — its content must also be available on the page the link opens.
- Keyboard. The card opens when the trigger gets focus and closes when focus leaves;
Esccloses it too. - Touch screens. It doesn't open on touch — a tap follows the link itself.
- No interactive content (buttons, forms) inside — keyboard users can't reach it. Use a
Popoverinstead.