# Hover Card

> A React hover card: a rich preview of a person, listing or link on hover or focus, opening as a bottom sheet on touch screens.

- Type: React component (Overlays)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add hover-card`
- Docs: https://wingo-ui.com/components/hover-card

## Interaction

Rest the pointer on a link or a name and after half a second the preview grows out of it on a quick spring. Move onto the card and it stays open, so its buttons and links work; leave both and it fades out 200ms later. Tab to the trigger and it opens the same way; Escape or moving focus on closes it. The card is a supplement: its content is skipped by Tab and every action in it is reachable elsewhere. On phones and tablets there is no hover, so the first tap opens the preview as a bottom sheet instead of following the link: drag it down, tap the dim page or press Escape to close it, and the sheet's own link leads on. With reduced motion it only fades.

## Installation

```bash
npx wingo-ui@latest add hover-card
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/hover-card` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/hover-card.tsx`

npm dependencies: `npm i @radix-ui/react-hover-card @radix-ui/react-slot motion`

Wingo UI dependencies (installed automatically):

- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))
- [Drawer](https://wingo-ui.com/components/drawer) ([Markdown](https://wingo-ui.com/components/drawer.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useMediaQuery](https://wingo-ui.com/components/use-media-query) ([Markdown](https://wingo-ui.com/components/use-media-query.md))

## Usage

```tsx
import { HoverCard } from "@/components/ui/hover-card"
import { Avatar } from "@/components/ui/avatar"

export function Mention() {
  return (
    <p className="text-sm">
      Ask{" "}
      <HoverCard
        trigger={
          <a href="/profile/emma.carter" className="font-semibold underline decoration-foreground/25 underline-offset-4">
            @emma.carter
          </a>
        }
        sheetTitle="Emma Carter's profile"
      >
        <div className="flex items-center gap-3">
          <Avatar src="/demo/ana-pop.jpg" name="Emma Carter" size="lg" status="online" className="[--avatar-ring:var(--popover)]" />
          <div className="min-w-0">
            <p className="text-[15px] font-semibold">Emma Carter</p>
            <p className="text-[13px] text-muted-foreground">Portland · member since 2019</p>
          </div>
        </div>
        <p className="mt-3 text-[13px] text-muted-foreground">48 listings · 4.9 ★ · replies in 1 h</p>
        <a href="/profile/emma.carter" className="mt-3 inline-block font-medium underline underline-offset-4">
          View profile
        </a>
      </HoverCard>{" "}
      about the bike.
    </p>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `trigger?` | `ReactNode` |  | What you hover, rendered asChild: an <a>, a router Link, a Button or an avatar wrapped in a button. It must pass a ref to its element. Plain text gets a focusable button with a dotted underline, for a name or a term inside a sentence. |
| `children?` | `ReactNode` |  | The preview: an avatar row and stats, a listing with its photo, an article with its image. Round inner parts with rounded-(--hover-card-inner-radius), which follows radius and padding (and is 16px in the sheet). Put a real link to the destination in it for touch screens. |
| `sheetTitle?` | `string` | `"Preview"` | The bottom sheet's title for screen readers. The sheet shows no header, but a dialog needs a name: say what it previews, like "Profilul Anei Pop". |
| `size?` | `"sm" \| "md" \| "lg" \| "auto"` | `"md"` | Width of the floating card: 256, 288 or 384px, or auto to fit the content (192px at least). Never wider than the screen minus 12px a side. The sheet always spans the phone. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | Corner radius of the card: 0, 12, 16 or 24px; full falls back to lg. The sheet keeps its own 28px top corners. |
| `padding?` | `"none" \| "sm" \| "md" \| "lg"` | `"md"` | Inner padding of the card: 0, 8, 16 or 20px. none lets a photo run to the edges (the card clips it to its corners), sm gives an inset photo with a matching smaller radius. The sheet always pads 20px. |
| `arrow?` | `boolean` | `false` | A small arrow with a rounded tip in the card's color, pointing at the trigger. Its sides carry on the card's hairline and it keeps clear of the corners. |
| `side?` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Where the card opens. It flips to the opposite side and slides along the edge when there is no room. |
| `align?` | `"start" \| "center" \| "end"` | `"center"` | Alignment along the trigger's edge. |
| `sideOffset?` | `number` | `8` | Distance from the trigger in px. The arrow adds its own 8px on top. |
| `openDelay?` | `number` | `500` | Hover (or keyboard focus) time in ms before it opens, so the pointer can cross a page full of links without cards popping up. 0 opens at once. |
| `closeDelay?` | `number` | `200` | Time in ms before it closes after the pointer leaves the trigger and the card. It is the grace period for travelling onto the card; at 0 it closes before you get there. |
| `responsive?` | `boolean` | `true` | On phones (below 768px) and touch screens (a coarse pointer, like an iPad at 1024px) the first tap on the trigger opens the preview as a bottom sheet instead of following the link. Off: the floating card everywhere, and a tap follows the link. |
| `defaultOpen?` | `boolean` | `false` | Starts open (uncontrolled). Leaving the trigger and the card, Escape or a click outside closes it. |
| `disabled?` | `boolean` | `false` | Never opens and closes if it is open, e.g. for your own name in a list. The trigger keeps working: a link navigates on click and on tap. |
| `open?` | `boolean` |  | Controls it from outside, with onOpenChange. |
| `onOpenChange?` | `(open: boolean) => void` |  | Called on hover, focus, a tap on touch screens, Escape and outside clicks. Not called for opens refused while disabled. |
| `container?` | `HTMLElement \| null` |  | Where the card (or the sheet) mounts. document.body by default. |
| `classNames?` | `Partial<Record<"content" \| "body" \| "arrow", string>>` |  | Extra classes for the card (hover-card-content, also the sheet), its padded scroll region (hover-card-body) and the arrow svg (set a fill-* to recolor it). |
| `className?` | `string` |  | Extra classes for the card, merged last. Other Radix content props pass through: alignOffset, collisionPadding (12), avoidCollisions, sticky, hideWhenDetached, onEscapeKeyDown, onPointerDownOutside. |

## Parts

### HoverCardRoot (component)

Compound API: holds the state and picks the mode once (the Radix hover card, or the drawer's bottom sheet on phones and touch screens). No DOM. Props: { open, defaultOpen, onOpenChange, openDelay, closeDelay, responsive, disabled }.

### HoverCardTrigger (component)

An <a> (or your child with asChild) that opens the card on hover and focus, or the sheet on a tap, where it keeps the link from navigating. A plain element gets data-slot="hover-card-trigger" and data-state; a component child keeps its own and gets data-hover-card-state. Props: ComponentProps<"a"> & { asChild?: boolean }.

### HoverCardContent (component)

The portaled, animated card (hover-card-content with data-side, data-align, data-size, data-state) and its body (hover-card-body); in sheet mode the drawer's sheet with the same slots and a hidden title. Props: { side, align, sideOffset, size, radius, padding, arrow, sheetTitle, container, classNames } & Radix HoverCard.Content props.

### useHoverCard (hook)

The nearest hover card's state, for content that lays out bigger in the sheet (44px buttons, a larger avatar) or closes the card after an action. Signature: () => { open: boolean, setOpen: (open: boolean) => void, sheet: boolean }.

## Examples

- **Listing preview**: padding="sm" insets the photo, whose corners follow --hover-card-inner-radius. The price is formatted in USD.
- **Sides with the arrow**: Four sides, each on a team avatar. size="auto" fits the text; the card flips when there is no room.
- **Instant vs default delays**: Plain text triggers get a dotted underline. 0 / 0 opens at once and closes the moment you leave.
- **Link preview**: padding="none" and size="lg": the cover runs to the card's edges and the card clips it.
- **On a phone**: Touch screens have no hover, so the first tap opens a bottom sheet with 48px buttons (useHoverCard).

## See also

- [Popover](https://wingo-ui.com/components/popover) ([Markdown](https://wingo-ui.com/components/popover.md))
- [Tooltip](https://wingo-ui.com/components/tooltip) ([Markdown](https://wingo-ui.com/components/tooltip.md))
- [Drawer](https://wingo-ui.com/components/drawer) ([Markdown](https://wingo-ui.com/components/drawer.md))
- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))

---

Canonical: https://wingo-ui.com/components/hover-card
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
