---
title: Hover Card
description: Content preview on hover
visibility: guest
draft: false
---

# Hover Card

A rich popup that appears on hover or focus — designed for previewing content like user profiles, link destinations, or product summaries. Similar to a [Tooltip](/components/tooltip) but intended for structured, interactive content.

## Basic Usage

:::preview{title="User Profile Hover Card"}
<x-wirekit::hover-card>
    <x-slot:trigger>
        <x-wirekit::button intent="neutral" surface="ghost" size="sm">@kevin</x-wirekit::button>
    </x-slot:trigger>
    <x-wirekit::row align="start" gap="sm">
        <x-wirekit::avatar name="Kevin Smith" size="lg" />
        <div>
            <div style="font-weight: 600; font-size: 0.9375rem;">Kevin Smith</div>
            <div style="font-size: 0.8125rem; color: var(--color-wk-text-muted);">Senior Developer</div>
            <div style="font-size: 0.8125rem; margin-top: 0.5rem;">Building things with Laravel &amp; Livewire.</div>
        </div>
    </x-wirekit::row>
</x-wirekit::hover-card>
:::

## Inline in Running Text

Wrap a word or phrase inside running prose with a hover card to surface a glossary entry, contributor profile, or product reference without the reader leaving the sentence. The trigger renders as an inline `<button>` styled to look like emphasized body text — fully keyboard-focusable, so the card opens on `Tab` as well as on hover.

:::preview{title="Bold word inside a sentence triggers a profile card"}
<x-wirekit::text>The release notes were authored by <x-wirekit::hover-card><x-slot:trigger><button type="button" style="all: unset; font-family: inherit; font-size: inherit; line-height: inherit; font-weight: 600; color: var(--color-wk-text); cursor: pointer; vertical-align: baseline; white-space: nowrap; text-decoration: underline dotted var(--color-wk-border-hover); text-underline-offset: 3px;">Kevin Smith</button></x-slot:trigger><x-wirekit::row align="start" gap="sm"><x-wirekit::avatar name="Kevin Smith" size="lg" /><div><div style="font-weight: 600; font-size: 0.9375rem;">Kevin Smith</div><div style="font-size: 0.8125rem; color: var(--color-wk-text-muted);">Senior Developer · Berlin</div><div style="font-size: 0.8125rem; margin-top: 0.5rem;">Maintainer of the auth + billing modules. Reach out for changes touching session handling.</div></div></x-wirekit::row></x-wirekit::hover-card> after a deep review of the migration plan.</x-wirekit::text>
:::

The button keeps `font: inherit` plus `vertical-align: baseline; line-height: inherit;` so it sits flush with the surrounding inline text — without the explicit baseline + line-height overrides the browser's default `<button>` baseline drops the trigger a few pixels and bumps the apparent line-height of the run, making the trigger label and the words after it look subtly larger than the words before. The whole sentence is also kept on a single source line so HTML's whitespace collapsing doesn't surface a double-space between the trigger and the next word.

The dotted underline is the conventional cue for a hoverable term in running prose; developers preferring a different affordance (solid underline, accent color) override the inline `style` attribute.

::: tip
Pause the cursor over the trigger label for ~300 ms — that's the default `delay-show` hover-intent buffer — and the profile card animates in below it. Keyboard users can `Tab` to the button to open it instantly. Lower the buffer with `<x-wirekit::hover-card :delay-show="100">` if the example feels sluggish inside a small preview frame.

The panel is teleported out of the document flow via Alpine's `x-teleport`, into WireKit's overlay landmark near the end of `<body>`, so the dialog's `<div>` markup lifts out of the surrounding `<p>` before the browser parses it — this keeps the running paragraph well-formed and the Alpine `$refs.panel` lookup intact across the teleport boundary. The landmark is created by WireKit's own JavaScript, so a teleported panel stays inside a region an assistive technology can announce.
:::

## Placement

Control where the card appears relative to the trigger:

:::preview{title="Top placement"}
<x-wirekit::hover-card placement="top">
    <x-slot:trigger>
        <x-wirekit::button intent="neutral" surface="outline" size="sm">Hover me (top)</x-wirekit::button>
    </x-slot:trigger>
    <div>This card appears above the trigger.</div>
</x-wirekit::hover-card>
:::

Available placements: `'top'`, `'bottom'` (default), `'left'`, `'right'`, and their `-start`/`-end` variants (e.g. `'bottom-start'`). The card automatically flips if there isn't enough space, powered by [Floating UI](https://floating-ui.com/).

## Custom Delays

Adjust show/hide delays for different use cases:

```blade
{{-- Quick preview: short delay --}}
<x-wirekit::hover-card :delay-show="100" :delay-hide="100">
    <x-slot:trigger>Quick preview</x-slot:trigger>
    Fast-appearing content.
</x-wirekit::hover-card>

{{-- Slow reveal: longer delay --}}
<x-wirekit::hover-card :delay-show="500" :delay-hide="300">
    <x-slot:trigger>Slow reveal</x-slot:trigger>
    Content that takes longer to appear.
</x-wirekit::hover-card>
```

## Hover Card vs Tooltip

| Feature | Tooltip | Hover Card |
| --- | --- | --- |
| Content | Plain text | Rich HTML (images, buttons, links) |
| Interaction | Read-only | Can contain interactive elements |
| ARIA role | `tooltip` | `dialog` |
| Keyboard | Focus shows, no interaction inside | Focus shows, can Tab into content |
| Use case | Short labels, abbreviations | User profiles, link previews |

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `placement` | string | `'bottom'` | Floating UI placement (`'top'`, `'bottom'`, `'left'`, `'right'`, + `-start`/`-end`) |
| `offset` | int | `8` | Distance from trigger in pixels |
| `delayShow` | int | `300` | Milliseconds before showing on hover |
| `delayHide` | int | `200` | Milliseconds before hiding on leave |
| `scope` | string\|null | `null` | Scoped personalization key |

### Slots

| Slot | Purpose |
| --- | --- |
| `trigger` | The element that activates the hover card |
| default | Rich content inside the card panel |

## Accessibility

- Trigger has `aria-haspopup="dialog"` — announces that activation opens a dialog
- Trigger has `aria-expanded` — toggles between `"true"` and `"false"`
- Panel uses `role="dialog"` — screen readers announce content as dialog
- **Keyboard:** focus on trigger opens card; Escape closes it
- **Mouse:** hovering trigger or panel keeps card open; moving away starts hide delay
- Card stays open when hovering between trigger and panel (bridge delay prevents flicker)
- SPA-safe: cleans up on Livewire page navigation

## Keyboard Interaction

| Key | Action |
|-----|--------|
| `Tab` (focusing the trigger) | Open the hover card |
| `Tab` (away from trigger) | Close the hover card |
| `Escape` | Close the hover card while the trigger keeps focus |

## Pitfalls

- **Don't put critical info in a hover-card.** Hover has no keyboard or touch equivalent — the component reveals on focus too, but discoverability for non-mouse users is poor. For must-read content use `<x-wirekit::popover>` with an explicit trigger.
- **Don't auto-open on page load.** Hover-cards are user-triggered.

## Design Tokens

| Element | Token |
| --- | --- |
| Panel background | `--color-wk-bg-elevated` |
| Panel border | `--color-wk-border` / `--border-wk-width` |
| Panel radius | `--radius-wk-lg` |
| Panel shadow | `--shadow-wk-lg` |
| Panel padding | `--padding-wk-x-md` |
| Text color | `--color-wk-text` |
| Text size | `--text-wk-md` |
| Font family | `--font-wk-sans` |
| Transition | `--transition-wk-duration` |

## Customization

Override defaults in `config/wirekit.php`:

```php
'components' => [
    'hover-card' => [],
],
```

## Further Reading

- [Floating UI documentation](https://floating-ui.com/)
- [WAI-ARIA: Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)
- [MDN: `aria-haspopup`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-haspopup)
