elements
Demos UI Feed Discuss Install
Style System
  • Style
  • Themes
  • Behaviors
Forms
  • Button
  • ButtonGroup
  • Checkbox
  • ColorPicker
  • Combobox
  • Field
  • FileUpload
  • Input
  • InputGroup
  • InputOTP
  • Label
  • NativeSelect
  • NumberInput
  • PasswordInput
  • RadioGroup
  • Select
  • Slider
  • Switch
  • TagsInput
  • Textarea
  • Toggle
  • ToggleGroup
Overlays
  • AlertDialog
  • Command
  • ContextMenu
  • Dialog
  • Drawer
  • DropdownMenu
  • HoverCard
  • Menubar
  • NavigationMenu
  • Popover
  • Sheet
  • Toast
  • Tooltip
Display
  • Accordion
  • Alert
  • AspectRatio
  • Avatar
  • Badge
  • Breadcrumb
  • Card
  • Collapsible
  • Empty
  • Item
  • Kbd
  • Pagination
  • Progress
  • Separator
  • Skeleton
  • Spinner
  • Table
  • Tabs
  • Tree
Layout
  • Carousel
  • Resizable
  • ScrollArea
  • Sidebar
Dates
  • Calendar
  • DatePicker
  • DateRangePicker
  • TimePicker
Data
  • AreaChart
  • BarChart
  • Chart
  • DataTable
  • Kanban
  • LineChart
  • PieChart
  • RadarChart
  • RadialChart
  • VirtualList
Chat
  • Attachment
  • Message
  • MessageList
  • PromptInput
Elements UI / Overlays / HoverCard

HoverCard

A preview of what a link points to, shown while the pointer rests on it.

Basic

Rest the pointer on the link, or Tab to it. The card opens after 700ms and stays open while the pointer moves into it.

Built with

@elements
and a lot of coffee.

Code
<HoverCard>
  <a slot="trigger" href="#">@elements</a>
  <div class="hover-card-media">
    <span class="avatar">EL</span>
    <div>
      <strong>@elements</strong>
      <p>One environment for building and deploying web apps.</p>
      <p class="meta">Joined October 2026</p>
    </div>
  </div>
</HoverCard>

Usage

import { HoverCard, HoverCardTrigger } from "@elements/ui/hover-card";

<HoverCard>
  <a slot="trigger" href="/u/ada">@ada</a>
  <div class="hover-card-media">
    <img src={user.avatar} alt="" width="40" height="40">
    <div>
      <strong>Ada Lovelace</strong>
      <p>Wrote the first program.</p>
    </div>
  </div>
</HoverCard>

Examples

Sides

side picks where the card opens; it flips when there is no room. arrow points at the trigger.

Code
<HoverCard side="top" size="auto" arrow>
  <Button slot="trigger" variant="ghost">Top</Button>
  Opens above.
</HoverCard>

RTL

inline-start and inline-end follow the text direction: in a right-to-left region inline-start opens on the right and inline-end on the left.

Code
<div dir="rtl">
  <HoverCard side="inline-start" size="auto" arrow>
    <Button slot="trigger" variant="ghost">بداية السطر</Button>
    يفتح على اليمين.
  </HoverCard>
  <HoverCard side="inline-end" size="auto" arrow>...</HoverCard>
</div>

No delay

openDelay={0} and closeDelay={0}: opens on the first hover, closes the moment the pointer leaves trigger and card. Moving to the neighbouring trigger swaps the cards.

instant
neighbour
Code
<HoverCard openDelay={0} closeDelay={0} size="sm">
  <a slot="trigger" href="#">instant</a>
  Opens on the first hover and closes as soon as the pointer leaves.
</HoverCard>
<HoverCard openDelay={0} closeDelay={0} size="sm">
  <a slot="trigger" href="#">neighbour</a>
  Moving from one trigger to the next swaps the cards.
</HoverCard>

Controlled

Bind open to state. onOpenChange reports the card's own changes, such as Escape or the pointer leaving.

Ada Lovelace
open: false changes:
Code
<Button onclick={() => state.preview = !state.preview}>Toggle</Button>
<HoverCard open={state.preview} onOpenChange={(open) => log(open)}>
  <a slot="trigger" href="#">Ada Lovelace</a>
  ...
</HoverCard>

Align and anchor

align lines the card up with the trigger's start, center or end. anchor positions it against another element on the page.

@start
@anchored
Profile row
Code
<HoverCard align="start">
  <a slot="trigger" href="/u/ada">@ada</a>
  ...
</HoverCard>
<HoverCard anchor="#profile-row">...</HoverCard>

Hide when detached

hideWhenDetached hides the card while its trigger is scrolled out of view.

@scrolls

Tab to the link, then scroll this box.

Code
<div class="scroller">
  <HoverCard hideWhenDetached>
    <a slot="trigger" href="/u/ada">@ada</a>
    ...
  </HoverCard>
</div>

Events

onOpenChangeComplete fires after each animation; onEscapeKeyDown and onPointerDownOutside fire before the card closes, and preventDefault keeps it open.

@events
@pinned
complete: ; escapes: 0; outside: 0 kept:
Code
<HoverCard onOpenChangeComplete={(open) => log(open)}
           onEscapeKeyDown={(e) => count(e)}
           onPointerDownOutside={(e) => count(e)}>...</HoverCard>

<Button onclick={() => state.pinned = !state.pinned}>Pin card</Button>
<HoverCard open={state.pinned}
           onEscapeKeyDown={(e) => e.preventDefault()}
           onPointerDownOutside={(e) => e.preventDefault()}>...</HoverCard>

Offsets and collisions

sideOffset and alignOffset move the card off its trigger. collisionPadding sets the room kept from the viewport edge; avoidCollisions={false} keeps it on its side with no flip or shift.

@offset 12, 8
@padding 24
@no-collisions
Code
<HoverCard align="start" sideOffset={12} alignOffset={8}>...</HoverCard>
<HoverCard align="end" collisionPadding={24} size="lg">...</HoverCard>
<HoverCard side="left" avoidCollisions={false}>...</HoverCard>

Collision avoidance and position method

collisionAvoidance tunes how it stays in view: side "none" keeps the side with no flip while align "shift" still slides it into view; side "shift" keeps the side and slides it over the trigger into view; fallbackAxisSide "start" tries the inline start side before the end. positionMethod="absolute" places it in page coordinates.

@stays-on-top
@slides-on-top
@start-side-first
@absolute
Code
<HoverCard side="top" collisionAvoidance={{ side: "none", align: "shift" }}>...</HoverCard>
<HoverCard side="top" collisionAvoidance={{ side: "shift" }}>...</HoverCard>
<HoverCard side="top" collisionAvoidance={{ fallbackAxisSide: "start" }}>...</HoverCard>
<HoverCard positionMethod="absolute">...</HoverCard>

Function offsets

sideOffset and alignOffset also take a function of the anchor's and the card's sizes, the side and the alignment.

@one-trigger-below
Code
<HoverCard sideOffset={(d) => d.anchor.height}>
  <a slot="trigger" href="/u/ada">@ada</a>
  ...
</HoverCard>

Collision boundary, sticky and arrow padding

collisionBoundary keeps the card inside an element as well as the viewport. sticky="always" keeps it in the boundary after its trigger leaves. arrowPadding keeps the arrow off the corners.

@inside-the-panel
@always
@partial

Open one, then scroll this box.

@ap
Code
<div id="panel">
  <HoverCard align="start" collisionBoundary="#panel" size="sm">...</HoverCard>
</div>

<HoverCard side="right" sticky="always" collisionBoundary="#list">...</HoverCard>
<HoverCard arrow align="start" arrowPadding={80}>...</HoverCard>

Change reasons

onOpenChange gets a second argument: the reason for the change, the event, and cancel() to keep the current state.

@reasons
@ignores-escape
reasons:
Code
<!-- details.trigger is the trigger element that caused the change, if one did -->
<HoverCard onOpenChange={(open, details) => log(open, details.reason, details.trigger)}>
  <a slot="trigger" href="/u/ada">@ada</a>
  ...
</HoverCard>

<!-- details.cancel() keeps the current state -->
<HoverCard onOpenChange={(open, details) => details.reason === "escape-key" && details.cancel()}>...</HoverCard>

Sizes

size sets the width: auto fits the content, sm, md (the default) and lg are fixed steps.

auto
sm
md
lg
Code
<HoverCard size="auto">...</HoverCard>
<HoverCard size="sm">...</HoverCard>
<HoverCard>...</HoverCard>            <!-- md -->
<HoverCard size="lg">...</HoverCard>

Constrain the content size

A tall card stays inside the viewport and scrolls; with an arrow the arrow stays visible outside the scroll box.

changelog
Code
<HoverCard arrow size="sm">
  <a slot="trigger" href="/changelog">changelog</a>
  <ul>...twenty releases...</ul>
</HoverCard>

Multiple triggers

HoverCardTrigger with a handle opens one card from several links. It sits against the link the pointer rests on and moves to the next one; payload carries that link's data, and PopoverViewport cross-fades the content.

Reviewed by @ada and @grace.

Code
<HoverCardTrigger handle="person" payload="Ada Lovelace"><a href="/u/ada">@ada</a></HoverCardTrigger>
<HoverCardTrigger handle="person" payload="Grace Hopper"><a href="/u/grace">@grace</a></HoverCardTrigger>

<!-- open, triggerId and payload are two-way: set triggerId and open to show it on a chosen link -->
<HoverCard handle="person" open={state.open} triggerId={state.link} payload={state.person} size="sm">
  <PopoverViewport>{state.person}</PopoverViewport>
</HoverCard>

Backdrop

backdrop dims the page under the open card. The pointer passes through it, so the link and the card keep working.

Read the

docs
first.

Code
<HoverCard backdrop>
  <a slot="trigger" href="/docs">docs</a>
  ...
</HoverCard>

Keep mounted

keepMounted keeps the closed card in the page, hidden, so what it holds stays as it was.

Ask

support
anything.

Code
<HoverCard keepMounted>
  <a slot="trigger" href="/support">support</a>
  <input aria-label="Note">   <!-- keeps what was typed while closed -->
</HoverCard>

Disable anchor tracking

disableAnchorTracking places the card once when it opens; it stays where it is while the page scrolls.

See

pricing
for plans.

Code
<HoverCard disableAnchorTracking>
  <a slot="trigger" href="/pricing">pricing</a>
  ...
</HoverCard>

Open on load

open={true} without a binding: the card is open when the page loads, then owns its state.

Code
<HoverCard open={true}>
  <a slot="trigger" href="/u/ada">@ada</a>
  Open when the page loads. The card owns open from here.
</HoverCard>
Reference HoverCard in the manual Every part, attribute, slot, key and data attribute.
Prev DropdownMenuNext Menubar
Product
Manual Pricing Compare
Company
History Contact
Legal
Privacy Policy Acceptable Use Terms License Agreement
© Elements Dev Corporation 2026