---
type: doc
title: Hover Card
description: For sighted users to preview content available behind a link.
---

> Fulldev UI for Astro. The `@fulldev` registry in `components.json` points at `https://ui.full.dev/r/styles/{style}/{name}.json`; see [installation](/astro/docs/installation.md).

```astro title="src/components/examples/hover-card/hover-card-demo.astro"
---
import { buttonVariants } from "@/components/ui/button"
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card"
---

<HoverCard delay={10} closeDelay={100}>
  <HoverCardTrigger class={buttonVariants({ variant: "link" })}>
    Hover Here
  </HoverCardTrigger>
  <HoverCardContent class="flex w-64 flex-col gap-0.5">
    <div class="font-semibold">@nextjs</div>
    <div>The React Framework – created and maintained by @vercel.</div>
    <div class="mt-1 text-xs text-muted-foreground">Joined December 2021</div>
  </HoverCardContent>
</HoverCard>
```

## Installation

```bash
npx shadcn@latest add @fulldev/hover-card
```

## Usage

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

```astro
<HoverCard>
  <HoverCardTrigger>Hover</HoverCardTrigger>
  <HoverCardContent>
    The React Framework – created and maintained by @vercel.
  </HoverCardContent>
</HoverCard>
```

`HoverCardTrigger` renders a `button`, or an `a` with `href`. Style it as a
button with `buttonVariants`, such as
`class={buttonVariants({ variant: "link" })}`.

## Composition

```text
HoverCard
├── HoverCardTrigger
└── HoverCardContent
```

## Trigger Delays

Set `delay` and `closeDelay` (in ms) on `HoverCard`, not on the trigger.

```astro
<HoverCard delay={100} closeDelay={200}>
  <HoverCardTrigger>Hover</HoverCardTrigger>
  <HoverCardContent>Content</HoverCardContent>
</HoverCard>
```

## Positioning

Use the `side` and `align` props on `HoverCardContent` to control placement.

```astro
<HoverCard>
  <HoverCardTrigger>Hover</HoverCardTrigger>
  <HoverCardContent side="top" align="start">
    Content
  </HoverCardContent>
</HoverCard>
```

## Basic

```astro title="src/components/examples/hover-card/hover-card-demo.astro"
---
import { buttonVariants } from "@/components/ui/button"
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card"
---

<HoverCard delay={10} closeDelay={100}>
  <HoverCardTrigger class={buttonVariants({ variant: "link" })}>
    Hover Here
  </HoverCardTrigger>
  <HoverCardContent class="flex w-64 flex-col gap-0.5">
    <div class="font-semibold">@nextjs</div>
    <div>The React Framework – created and maintained by @vercel.</div>
    <div class="mt-1 text-xs text-muted-foreground">Joined December 2021</div>
  </HoverCardContent>
</HoverCard>
```

## Sides

```astro title="src/components/examples/hover-card/hover-card-sides.astro"
---
import { buttonVariants } from "@/components/ui/button"
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card"

const HOVER_CARD_SIDES = ["left", "top", "bottom", "right"] as const
---

<div class="flex flex-wrap justify-center gap-2">
  {HOVER_CARD_SIDES.map((side) => (
    <HoverCard delay={100} closeDelay={100}>
      <HoverCardTrigger
        class={buttonVariants({ variant: "outline", class: "capitalize" })}
      >
        {side}
      </HoverCardTrigger>
      <HoverCardContent side={side}>
        <div class="flex flex-col gap-1">
          <h4 class="font-medium">Hover Card</h4>
          <p>This hover card appears on the {side} side of the trigger.</p>
        </div>
      </HoverCardContent>
    </HoverCard>
  ))}
</div>
```

## RTL

`side` takes physical sides only (`top`, `right`, `bottom`, `left`), so the
example resolves `inline-start` and `inline-end` from `dir`.

```astro title="src/components/examples/hover-card/hover-card-rtl.astro"
---
import { buttonVariants } from "@/components/ui/button"
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card"

const translations = {
  en: {
    dir: "ltr",
    values: {
      trigger: "Wireless Headphones",
      name: "Wireless Headphones",
      price: "$99.99",
      "inline-start": "Inline Start",
      left: "Left",
      top: "Top",
      bottom: "Bottom",
      right: "Right",
      "inline-end": "Inline End",
    },
  },
  ar: {
    dir: "rtl",
    values: {
      trigger: "سماعات لاسلكية",
      name: "سماعات لاسلكية",
      price: "٩٩.٩٩ $",
      "inline-start": "بداية السطر",
      left: "يسار",
      top: "أعلى",
      bottom: "أسفل",
      right: "يمين",
      "inline-end": "نهاية السطر",
    },
  },
  he: {
    dir: "rtl",
    values: {
      trigger: "אוזניות אלחוטיות",
      name: "אוזניות אלחוטיות",
      price: "99.99 $",
      "inline-start": "תחילת השורה",
      left: "שמאל",
      top: "למעלה",
      bottom: "למטה",
      right: "ימין",
      "inline-end": "סוף השורה",
    },
  },
} as const

const physicalSides = ["left", "top", "bottom", "right"] as const
const logicalSides = ["inline-start", "inline-end"] as const

const { dir, values: t } = translations.ar

// HoverCard takes physical sides only: resolve the logical ones for `dir`.
const physicalSide = (side: (typeof logicalSides)[number]) =>
  (side === "inline-start") === (dir === "rtl") ? "right" : "left"
---

<div class="grid gap-4">
  <div class="flex flex-wrap justify-center gap-2">
    {physicalSides.map((side) => (
      <HoverCard delay={10} closeDelay={100}>
        <HoverCardTrigger class={buttonVariants({ variant: "outline" })}>
          {t[side]}
        </HoverCardTrigger>
        <HoverCardContent
          side={side}
          class="flex w-64 flex-col gap-1"
          dir={dir}
        >
          <div class="font-semibold">{t.name}</div>
          <div class="text-sm text-muted-foreground">{t.price}</div>
        </HoverCardContent>
      </HoverCard>
    ))}
  </div>
  <div class="flex flex-wrap justify-center gap-2">
    {logicalSides.map((side) => (
      <HoverCard delay={10} closeDelay={100}>
        <HoverCardTrigger class={buttonVariants({ variant: "outline" })}>
          {t[side]}
        </HoverCardTrigger>
        <HoverCardContent
          side={physicalSide(side)}
          class="flex w-64 flex-col gap-1"
          dir={dir}
        >
          <div class="font-semibold">{t.name}</div>
          <div class="text-sm text-muted-foreground">{t.price}</div>
        </HoverCardContent>
      </HoverCard>
    ))}
  </div>
</div>
```

## Open state

Use `defaultOpen` for an initially open card that still responds to pointer and
keyboard interactions. The `open` prop enables controlled mode: interactions
request changes but do not update the state themselves. Dispatch `hover-card:set`
with `{ detail: { open: false } }` (or `true`) on the root to update it.

## Content mounting

Data Slot's recommended `"lazy"` strategy is the default: closed content is
retained but detached from the document. Fulldev initializes nested Data Slot
components inside that retained content too.

Use `mountStrategy="eager"` when an integration needs to query closed content
through the document. Both strategies include content in server-rendered HTML;
neither adds a no-JavaScript fallback.

## API Reference

Source: [`src/components/ui/hover-card`](https://github.com/fulldotdev/ui/tree/main/src/components/ui/hover-card)

| `HoverCard` prop      | Type                                     | Default    |
| --------------------- | ---------------------------------------- | ---------- |
| `delay`               | `number`                                 | `700`      |
| `closeDelay`          | `number`                                 | `300`      |
| `skipDelayDuration`   | `number`                                 | `300`      |
| `defaultOpen`         | `boolean`                                |            |
| `open`                | `boolean`                                |            |
| `side`                | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` |
| `align`               | `"start" \| "center" \| "end"`           | `"center"` |
| `sideOffset`          | `number`                                 | `4`        |
| `alignOffset`         | `number`                                 | `0`        |
| `avoidCollisions`     | `boolean`                                | `true`     |
| `collisionPadding`    | `number`                                 | `8`        |
| `portal`              | `boolean`                                | `true`     |
| `closeOnClickOutside` | `boolean`                                | `true`     |
| `closeOnEscape`       | `boolean`                                | `true`     |
| `mountStrategy`       | `"lazy" \| "eager"`                      | `"lazy"`   |

`HoverCardContent` takes `side`, `align`, `sideOffset`, `alignOffset`,
`avoidCollisions` and `collisionPadding` to override the root per content.
See the [Data Slot docs](https://github.com/bejamas/data-slot/blob/main/packages/hover-card/README.md)
for events and behavior.

## Install what this page shows

```bash
npx shadcn@latest add @fulldev/hover-card-examples
```
