# Hover Card

A card that previews content behind a link when people hover over or focus it.

Hover Card is an open-source component from suiss/ui for React. The @esuiss/ui CLI copies its source into your project, so you own the code.

Source: https://ui.suiss.app/docs/components/hover-card

**React**

**Example**

```tsx
import { CalendarBlankIcon } from "@phosphor-icons/react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { buttonVariants } from "@/components/ui/button"
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card"

export default function HoverCardDemo() {
  return (
    <HoverCard>
      <HoverCardTrigger
        href="#"
        className={buttonVariants({ variant: "link" })}
      >
        @nextjs
      </HoverCardTrigger>
      <HoverCardContent className="w-80">
        <div className="flex gap-4">
          <Avatar size="lg">
            <AvatarImage src="https://github.com/vercel.png" alt="@nextjs" />
            <AvatarFallback>NJ</AvatarFallback>
          </Avatar>
          <div className="flex flex-col gap-1">
            <h4 className="text-sm font-semibold">@nextjs</h4>
            <p className="text-sm">
              The React framework, created and maintained by @vercel.
            </p>
            <div className="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
              <CalendarBlankIcon />
              Joined December 2021
            </div>
          </div>
        </div>
      </HoverCardContent>
    </HoverCard>
  )
}
```

`HoverCard` wraps the hover card primitive of the chosen base. The `HoverCard` root manages the open state, `HoverCardTrigger` renders the link that opens it, and `HoverCardContent` renders the floating card in a portal, positioned next to the trigger.

Use a hover card for supplementary, non-essential previews, such as a profile summary behind a username. Because it only opens on hover or focus, do not put content there that people need to complete a task. For interactive content opened with a click, use a popover.

## Installation

### CLI

```bash title="Terminal"
npx shadcn@latest add @esuiss/hover-card
```

Works in any project with a `components.json`. See [Installation](https://ui.suiss.app/docs/installation#add-components) for the one-line registry setup. Or use the suiss/ui CLI:

```bash title="Terminal"
npx @esuiss/ui@latest add hover-card
```

### Manual

1. Install the dependencies and add the [`cn` helper](https://ui.suiss.app/docs/installation#the-cn-helper):

```bash title="Terminal"
npm install @base-ui/react
```

2. Copy the source into `components/ui/hover-card.tsx`:

```tsx title="components/ui/hover-card.tsx"
"use client"

import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card"
import { cn } from "@/lib/utils"

function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {
  return <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />
}

function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {
  return (
    <PreviewCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
  )
}

function HoverCardContent({
  className,
  side = "bottom",
  sideOffset = 4,
  align = "center",
  alignOffset = 4,
  ...props
}: PreviewCardPrimitive.Popup.Props &
  Pick<
    PreviewCardPrimitive.Positioner.Props,
    "align" | "alignOffset" | "side" | "sideOffset"
  >) {
  return (
    <PreviewCardPrimitive.Portal data-slot="hover-card-portal">
      <PreviewCardPrimitive.Positioner
        align={align}
        alignOffset={alignOffset}
        side={side}
        sideOffset={sideOffset}
        className="isolate z-50"
      >
        <PreviewCardPrimitive.Popup
          data-slot="hover-card-content"
          className={cn(
            "z-50 w-72 origin-(--transform-origin) rounded-2xl bg-popover p-4 text-base text-popover-foreground shadow-lg ring-1 ring-foreground/5 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-start-2 data-[side=inline-start]:slide-in-from-end-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 dark:ring-foreground/10 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
            className
          )}
          {...props}
        />
      </PreviewCardPrimitive.Positioner>
    </PreviewCardPrimitive.Portal>
  )
}

export { HoverCard, HoverCardContent, HoverCardTrigger }
```

3. Update the import paths to match your project.

## Usage

```tsx
import { HoverCard, HoverCardTrigger, HoverCardContent } from "@/components/ui/hover-card"
```

```tsx
<HoverCard>
  <HoverCardTrigger
    href="#"
    className={buttonVariants({ variant: "link" })}
  >
    @nextjs
  </HoverCardTrigger>
  <HoverCardContent className="w-80">
    <div className="flex gap-4">
      <Avatar size="lg">
        <AvatarImage src="https://github.com/vercel.png" alt="@nextjs" />
        <AvatarFallback>NJ</AvatarFallback>
      </Avatar>
      <div className="flex flex-col gap-1">
        <h4 className="text-sm font-semibold">@nextjs</h4>
        <p className="text-sm">
          The React framework, created and maintained by @vercel.
        </p>
        <div className="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
          <CalendarBlankIcon />
          Joined December 2021
        </div>
      </div>
    </div>
  </HoverCardContent>
</HoverCard>
```

The trigger renders an anchor, so give it an `href`. Position the card with the `side`, `sideOffset`, `align` and `alignOffset` props on `HoverCardContent`; by default it opens on the `bottom`, centered, with a `4` pixel offset. Set `defaultOpen` on the root to start open, or control it with `open` and `onOpenChange`.

## Examples

### Sides

Four hover cards opening on the `top`, `right`, `bottom` and `left` sides of their triggers.

**Example**

```tsx
import { buttonVariants } from "@/components/ui/button"
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card"

export default function HoverCardSides() {
  return (
    <div className="flex flex-wrap items-center gap-3 p-24">
      {(["top", "right", "bottom", "left"] as const).map((side) => (
        <HoverCard key={side}>
          <HoverCardTrigger
            href="#"
            className={buttonVariants({ variant: "outline" })}
          >
            {side.charAt(0).toUpperCase() + side.slice(1)}
          </HoverCardTrigger>
          <HoverCardContent side={side} className="w-56">
            <p className="text-sm">
              This card opens on the {side} side of its trigger.
            </p>
          </HoverCardContent>
        </HoverCard>
      ))}
    </div>
  )
}
```

## API Reference

- `HoverCard`
- `HoverCardTrigger`
- `HoverCardContent`

### HoverCard

The root that manages the card's open state.

Also accepts the props of the primitive part it wraps.

### HoverCardTrigger

The link that opens the card on hover or focus.

Also accepts the props of the primitive part it wraps.

### HoverCardContent

The floating card, rendered in a portal and positioned relative to the trigger.

Also accepts the props of the primitive parts it wraps.

## Accessibility

- The trigger renders as a link and is reachable with Tab.
- The card opens when the pointer rests on the trigger or when the trigger receives keyboard focus, and closes when the pointer leaves.
- The card is a visual preview; make sure the same information is available elsewhere, for example on the linked page.

## Notes

- Styling hooks: `data-slot` values `hover-card-trigger` and `hover-card-content`. The root renders no element of its own.
- `HoverCardContent` slides in from the side it opens on, and fades and zooms as it opens and closes.
- The card is `w-72` by default; override it with `className`.
- Uses the `popover` and `popover-foreground` colors.

