# Hover Card

A preview card that appears while a link is hovered or focused, for supplementary content.

Docs: https://corsairui.vercel.app/docs/hover-card

## Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/hover-card
```

Also adds: `utils`.
npm: `@radix-ui/react-hover-card`, `clsx`, `tailwind-merge`.

## API

Built on Radix Hover Card (https://www.radix-ui.com/primitives/docs/components/hover-card); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `openDelay / closeDelay` | `number` | `400 / 150` | HoverCard: in ms, long enough to move onto the card. |
| `align / sideOffset` | `"start" \| "center" \| "end" / number` | `"center" / 4` | HoverCardContent: position next to the trigger. |

## Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `hover-card`, `hover-card-content`, `hover-card-trigger`

## Accessibility

- Opens on hover and on keyboard focus of the trigger.
- Touch and screen reader users may never see it: the link must work on its own and the card holds nothing essential.
- It appears without fading or zooming with prefers-reduced-motion.

## Examples

On the docs page: Profile preview. The usage examples in the source comments below are the reference.

## Source

What `shadcn add` writes into the project:

### hover-card.tsx

```tsx
"use client";

import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
import type { ComponentProps } from "react";

import { cn } from "@/registry/default/lib/utils";

/**
 * A preview card that appears while the pointer rests on a link, or while
 * the link has keyboard focus, and goes away when either leaves. Use it for
 * supplementary content only: touch and screen reader users may never see
 * the card, so the trigger must lead somewhere on its own (a profile page,
 * the full article) and nothing in the card may be the only way to do
 * something. The card opens after `openDelay` (400ms) and closes after
 * `closeDelay` (150ms), long enough to move the pointer onto it. With
 * reduced motion it appears without fading or zooming.
 *
 * @example
 * <HoverCard>
 *   <HoverCardTrigger asChild>
 *     <a href="/users/ada">@ada</a>
 *   </HoverCardTrigger>
 *   <HoverCardContent>
 *     <p className="font-medium">Ada Lovelace</p>
 *     <p className="text-muted-foreground">Writes about analytical engines.</p>
 *   </HoverCardContent>
 * </HoverCard>
 */
function HoverCard({
  openDelay = 400,
  closeDelay = 150,
  ...props
}: ComponentProps<typeof HoverCardPrimitive.Root>) {
  return (
    <HoverCardPrimitive.Root
      data-slot="hover-card"
      openDelay={openDelay}
      closeDelay={closeDelay}
      {...props}
    />
  );
}

/** The link the card belongs to. It renders an `<a>`; use `asChild` for your router's link. */
function HoverCardTrigger(props: ComponentProps<typeof HoverCardPrimitive.Trigger>) {
  return <HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />;
}

/** The card, rendered in a portal next to the trigger. */
function HoverCardContent({
  className,
  align = "center",
  sideOffset = 4,
  ...props
}: ComponentProps<typeof HoverCardPrimitive.Content>) {
  return (
    <HoverCardPrimitive.Portal>
      <HoverCardPrimitive.Content
        data-slot="hover-card-content"
        align={align}
        sideOffset={sideOffset}
        className={cn(
          "bg-popover text-popover-foreground z-50 w-64 origin-[var(--radix-hover-card-content-transform-origin)] rounded-md border p-4 shadow-md outline-none",
          // animate-in/out sit behind motion-safe: so reduced motion really turns them off.
          "motion-safe:data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
          "motion-safe:data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
          "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
          className
        )}
        {...props}
      />
    </HoverCardPrimitive.Portal>
  );
}

export { HoverCard, HoverCardContent, HoverCardTrigger };
```
