---
title: "Hover Card"
description: "For sighted users to preview content available behind a link."
canonical_url: "https://vinicunca.dev/akar/components/hover-card"
---
# Hover Card

> For sighted users to preview content available behind a link.

## HoverCard

```vue [index.vue]
<script setup lang="ts">
import { HoverCardArrow, HoverCardContent, HoverCardPortal, HoverCardRoot, HoverCardTrigger } from 'akar'
import { ref } from 'vue'
import './styles.css'

const hoverState = ref(false)
</script>

<template>
  <div class="akar-demo akar-demo-HoverCard">
    <HoverCardRoot v-model:open="hoverState">
        <HoverCardTrigger
          class="ImageTrigger"
          href="https://twitter.com/unovue"
          target="_blank"
          rel="noreferrer noopener"
        >
          <img
            class="Image normal"
            src="https://pbs.twimg.com/profile_images/1833445381986045952/jwoHkIIq_400x400.jpg"
            alt="Radix UI"
          >
        </HoverCardTrigger>
        <HoverCardPortal>
          <HoverCardContent
            class="HoverCardContent"
            :side-offset="5"
          >
            <div :style="{ display: 'flex', flexDirection: 'column', gap: 7 }">
              <img
                class="Image large"
                src="https://pbs.twimg.com/profile_images/1833445381986045952/jwoHkIIq_400x400.jpg"
                alt="Radix UI"
              >
              <div :style="{ display: 'flex', flexDirection: 'column', gap: 15 }">
                <div>
                  <div class="Text bold">
                    Radix
                  </div>
                  <div class="Text faded">
                    @radix_ui
                  </div>
                </div>
                <div class="Text">
                  Components, icons, colors, and templates for building high-quality, accessible UI. Free and open-source.
                </div>
                <div :style="{ display: 'flex', gap: 15 }">
                  <div :style="{ display: 'flex', gap: 5 }">
                    <div class="Text bold">
                      0
                    </div>
                    <div class="Text faded">
                      Following
                    </div>
                  </div>
                  <div :style="{ display: 'flex', gap: 5 }">
                    <div class="Text bold">
                      2,900
                    </div>
                    <div class="Text faded">
                      Followers
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <HoverCardArrow
              class="HoverCardArrow"
              :width="8"
            />
          </HoverCardContent>
        </HoverCardPortal>
      </HoverCardRoot>
  </div>
</template>
```

```css [styles.css]
@import '../_colors/mauve.css';

@scope (.akar-demo-HoverCard) {
/* reset */
a {
  all: unset;
}

.HoverCardContent {
  border-radius: 6px;
  padding: 20px;
  width: 300px;
  background-color: white;
  box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
  animation-duration: 400ms;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform, opacity;
}
.HoverCardContent[data-side='top'] {
  animation-name: slideDownAndFade;
}
.HoverCardContent[data-side='right'] {
  animation-name: slideLeftAndFade;
}
.HoverCardContent[data-side='bottom'] {
  animation-name: slideUpAndFade;
}
.HoverCardContent[data-side='left'] {
  animation-name: slideRightAndFade;
}

.HoverCardArrow {
  fill: white;
}

.ImageTrigger {
  cursor: pointer;
  border-radius: 100%;
  display: inline-block;
}
.ImageTrigger:focus {
  box-shadow: 0 0 0 2px white;
}

.Image {
  display: block;
  border-radius: 100%;
}
.Image.normal {
  width: 45px;
  height: 45px;
}
.Image.large {
  width: 60px;
  height: 60px;
}

.Text {
  margin: 0;
  color: var(--mauve-12);
  font-size: 15px;
  line-height: 1.5;
}
.Text.faded {
  color: var(--mauve-10);
}
.Text.bold {
  font-weight: 500;
}

@keyframes slideUpAndFade {
  0% {
    opacity: 0;
    transform: translateY(2px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideRightAndFade {
  0% {
    opacity: 0;
    transform: translateX(-2px);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideDownAndFade {
  0% {
    opacity: 0;
    transform: translateY(-2px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideLeftAndFade {
  0% {
    opacity: 0;
    transform: translateX(2px);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}
}
```

## Features

- Can be controlled or uncontrolled.
- Customize side, alignment, offsets, collision handling.
- Optionally render a pointing arrow.
- Supports custom open and close delays.
- Ignored by screen readers.

## Installation

Install the component from your command line.

```bash [pnpm]
pnpm add akar
```

```bash [yarn]
yarn add akar
```

```bash [npm]
npm install akar
```

```bash [bun]
bun add akar
```

## Anatomy

Import all parts and piece them together.

```vue
<script setup>
import { HoverCardArrow, HoverCardContent, HoverCardPortal, HoverCardRoot, HoverCardTrigger } from 'akar';
</script>

<template>
  <HoverCardRoot>
    <HoverCardTrigger />
    <HoverCardPortal>
      <HoverCardContent>
        <HoverCardArrow />
      </HoverCardContent>
    </HoverCardPortal>
  </HoverCardRoot>
</template>
```

## API Reference

### Root

Contains all the parts of a hover card.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `closeDelay` | `300` | `number` | The duration from when the mouse leaves the trigger or content until the hover card closes. |
| `defaultOpen` | `false` | `boolean` | The open state of the hover card when it is initially rendered. Use when you do not need to control its open state. |
| `enableTouch` | `false` | `boolean` | When `true`, tapping the trigger on touch devices toggles the hover card open/closed. By default touch interactions are ignored to match pointer hover semantics. |
| `open` |  | `boolean` | The controlled open state of the hover card. Can be binded as `v-model:open`. |
| `openDelay` | `700` | `number` | The duration from when the mouse enters the trigger until the hover card opens. |

**Events**

| Event | Payload | Description |
| --- | --- | --- |
| `update:open` | `value: boolean` | Event handler called when the open state of the hover card changes. |

**Slots**

| Slot | Payload | Description |
| --- | --- | --- |
| `open` | `boolean` | Current open state |

[HoverCardRoot.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/HoverCard/HoverCardRoot.vue)

### Trigger

The link that opens the hover card when hovered.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"a"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `reference` |  | `ReferenceElement` | The reference (or anchor) element that is being referred to for positioning.<br><br>If not provided will use the current component as anchor. |

[HoverCardTrigger.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/HoverCard/HoverCardTrigger.vue)

| Data attribute | Value |
| --- | --- |
| `[data-state]` | "open" \\| "closed" |

### Portal

When used, portals the content part into the `body`.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `defer` |  | `boolean` | Defer the resolving of a Teleport target until other parts of the<br>application have mounted (requires Vue 3.5.0+)<br><br>[reference](https://vuejs.org/guide/built-ins/teleport.html#deferred-teleport) |
| `disabled` |  | `boolean` | Disable teleport and render the component inline<br><br>[reference](https://vuejs.org/guide/built-ins/teleport.html#disabling-teleport) |
| `forceMount` |  | `boolean` | Used to force mounting when more control is needed. Useful when<br>controlling animation with Vue animation libraries. |
| `to` |  | `string \| HTMLElement` | Vue native teleport component prop `:to`<br><br>[reference](https://vuejs.org/guide/built-ins/teleport.html#basic-usage) |

[HoverCardPortal.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/HoverCard/HoverCardPortal.vue)

### Content

The component that pops out when the hover card is open.

> [!NOTE]
> 
> Defer mounting with `forceMount` / [`Presence`](https://vinicunca.dev/akar/utilities/presence). See the [animation guide](https://vinicunca.dev/akar/guides/animation).

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `align` | `"center"` | `"start" \| "center" \| "end"` | The preferred alignment against the trigger.<br>May change when collisions occur. |
| `alignFlip` | `true` | `boolean` | Flip alignment when colliding with boundary.<br>May only occur when `prioritizePosition` is true. |
| `alignOffset` | `0` | `number` | An offset in pixels from the `start` or `end` alignment options. |
| `arrowPadding` | `0` | `number` | The padding between the arrow and the edges of the content.<br>If your content has border-radius, this will prevent it from<br>overflowing the corners. |
| `as` | `"div"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `avoidCollisions` | `true` | `boolean` | When `true`, overrides the side and align preferences<br>to prevent collisions with boundary edges. |
| `collisionBoundary` | `[]` | `Element \| (Element \| null)[] \| null` | The element used as the collision boundary. By default<br>this is the viewport, though you can provide additional<br>element(s) to be included in this check. |
| `collisionPadding` | `0` | `number \| Partial<Record<"top" \| "right" \| "bottom" \| "left", number>>` | The distance in pixels from the boundary edges where collision<br>detection should occur. Accepts a number (same for all sides),<br>or a partial padding object, for example: { top: 20, left: 20 }. |
| `dir` |  | `"ltr" \| "rtl"` | The reading direction of the popper content when applicable.<br><br>If omitted, inherits globally from `ConfigProvider` or assumes LTR (left-to-right) reading mode. |
| `disableUpdateOnLayoutShift` | `false` | `boolean` | Whether to disable the update position for the content when the layout shifted. |
| `forceMount` |  | `boolean` | Used to force mounting when more control is needed. Useful when<br>controlling animation with Vue animation libraries. |
| `hideShiftedArrow` | `true` | `boolean` | When `true`, hides the arrow when it cannot be centered<br>to the reference element. |
| `hideWhenDetached` | `false` | `boolean` | Whether to hide the content when the trigger becomes fully occluded. |
| `memoDependencies` |  | `unknown[]` | Reactive dependencies that should invalidate the memoized content subtree. |
| `positionStrategy` |  | `"fixed" \| "absolute"` | The type of CSS position property to use. |
| `prioritizePosition` | `false` | `boolean` | Force content to be position within the viewport.<br><br>Might overlap the reference element, which may not be desired. |
| `reference` |  | `ReferenceElement` | The custom element or virtual element that will be set as the reference<br>to position the floating element.<br><br>If provided, it will replace the default anchor element. |
| `side` | `"bottom"` | `"top" \| "right" \| "bottom" \| "left"` | The preferred side of the trigger to render against when open.<br>Will be reversed when collisions occur and avoidCollisions<br>is enabled. |
| `sideFlip` | `true` | `boolean` | Flip to the opposite side when colliding with boundary. |
| `sideOffset` | `0` | `number` | The distance in pixels from the trigger. |
| `sticky` | `"partial"` | `"partial" \| "always"` | The sticky behavior on the align axis. `partial` will keep the<br>content in the boundary as long as the trigger is at least partially<br>in the boundary whilst "always" will keep the content in the boundary<br>regardless. |
| `updatePositionStrategy` | `'optimized'` | `"always" \| "optimized"` | Strategy to update the position of the floating element on every animation frame. |

**Events**

| Event | Payload | Description |
| --- | --- | --- |
| `escapeKeyDown` | `event: KeyboardEvent` | Event handler called when the escape key is down.<br>Can be prevented. |
| `focusOutside` | `event: FocusOutsideEvent` | Event handler called when the focus moves outside of the `DismissableLayer`.<br>Can be prevented. |
| `interactOutside` | `event: PointerDownOutsideEvent \| FocusOutsideEvent` | Event handler called when an interaction happens outside the `DismissableLayer`.<br>Specifically, when a `pointerdown` event happens outside or focus moves outside of it.<br>Can be prevented. |
| `pointerDownOutside` | `event: PointerDownOutsideEvent` | Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`.<br>Can be prevented. |

[HoverCardContent.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/HoverCard/HoverCardContent.vue)

| Data attribute | Value |
| --- | --- |
| `[data-state]` | "open" \\| "closed" |
| `[data-side]` | "left" \\| "right" \\| "bottom" \\| "top" |
| `[data-align]` | "start" \\| "end" \\| "center" |

| CSS variable | Description |
| --- | --- |
| `--akar-hover-card-content-transform-origin` | The `transform-origin` computed from the content and arrow positions/offsets |
| `--akar-hover-card-content-available-width` | The remaining width between the trigger and the boundary edge |
| `--akar-hover-card-content-available-height` | The remaining height between the trigger and the boundary edge |
| `--akar-hover-card-trigger-width` | The width of the trigger |
| `--akar-hover-card-trigger-height` | The height of the trigger |

### Arrow

An optional arrow element to render alongside the hover card. This can be used to help visually link the trigger with the `HoverCardContent`. Must be rendered inside `HoverCardContent`.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"svg"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `height` | `5` | `number` | The height of the arrow in pixels. |
| `rounded` | `false` | `boolean` | When `true`, render the rounded version of arrow. Do not work with `as`/`asChild` |
| `width` | `10` | `number` | The width of the arrow in pixels. |

[HoverCardArrow.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/HoverCard/HoverCardArrow.vue)

## Examples

### Show instantly

Use the `openDelay` prop to control the time it takes for the hover card to open.

```vue
<script setup>
import {
  HoverCardArrow,
  HoverCardContent,
  HoverCardPortal,
  HoverCardRoot,
  HoverCardTrigger,
} from 'akar'
</script>

<template>
  <HoverCardRoot :open-delay="0">
    <HoverCardTrigger>…</HoverCardTrigger>
    <HoverCardContent>…</HoverCardContent>
  </HoverCardRoot>
</template>
```

### Constrain the content size

You may want to constrain the width of the content so that it matches the trigger width. You may also want to constrain its height to not exceed the viewport.

We expose several CSS custom properties such as `--akar-hover-card-trigger-width` and `--akar-hover-card-content-available-height` to support this. Use them to constrain the content dimensions.

```vue
// index.vue
<script setup>
import { HoverCardArrow, HoverCardContent, HoverCardPortal, HoverCardRoot, HoverCardTrigger } from 'akar'
</script>

<template>
  <HoverCardRoot>
    <HoverCardTrigger>…</HoverCardTrigger>
    <HoverCardPortal>
      <HoverCardContent
        class="HoverCardContent"
        :side-offset="5"
      >
        …
      </HoverCardContent>
    </HoverCardPortal>
  </HoverCardRoot>
</template>
```

```css
/* styles.css */
.HoverCardContent {
  width: var(--akar-hover-card-trigger-width);
  max-height: var(--akar-hover-card-content-available-height);
}
```

### Origin-aware animations

We expose a CSS custom property `--akar-hover-card-content-transform-origin`. Use it to animate the content from its computed origin based on `side`, `sideOffset`, `align`, `alignOffset` and any collisions.

```vue
<script setup>
import { HoverCardArrow, HoverCardContent, HoverCardPortal, HoverCardRoot, HoverCardTrigger } from 'akar'
</script>

<template>
  <HoverCardRoot>
    <HoverCardTrigger>…</HoverCardTrigger>
    <HoverCardContent class="HoverCardContent">
      …
    </HoverCardContent>
  </HoverCardRoot>
</template>
```

```css
/* styles.css */
.HoverCardContent {
  transform-origin: var(--akar-hover-card-content-transform-origin);
  animation: scaleIn 0.5s ease-out;
}

@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
```

### Collision-aware animations

We expose `data-side` and `data-align` attributes. Their values will change at runtime to reflect collisions. Use them to create collision and direction-aware animations.

```vue
<script setup>
import { HoverCardArrow, HoverCardContent, HoverCardPortal, HoverCardRoot, HoverCardTrigger } from 'akar'
</script>

<template>
  <HoverCardRoot>
    <HoverCardTrigger>…</HoverCardTrigger>
    <HoverCardContent class="HoverCardContent">
      …
    </HoverCardContent>
  </HoverCardRoot>
</template>
```

```css
/* styles.css */
.HoverCardContent {
  animation-duration: 0.6s;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.HoverCardContent[data-side="top"] {
  animation-name: slideUp;
}
.HoverCardContent[data-side="bottom"] {
  animation-name: slideDown;
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideDown {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
```

## Accessibility

The hover card is intended for sighted users only, the content will be inaccessible to keyboard users.

### Keyboard Interactions

| Key | Description |
| --- | --- |
| <kbd>Tab</kbd> | Opens/closes the hover card. |
| <kbd>Enter</kbd> | Opens the hover card link |

---

- [GitHub](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/HoverCard)
- [npm](https://www.npmjs.com/package/akar)


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
