Hover Card
A preview surface for sighted users — a link-shaped trigger, configurable open and close delays, and side/align positioning.
- Status
- Since
0.1.0- Accessibility pattern
- sighted-preview only; the trigger carries the content
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Hover Card before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify hover-cardInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/hover-cardRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
Usage
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
<HoverCard>
<HoverCardTrigger>Hover</HoverCardTrigger>
<HoverCardContent>
The React Framework – created and maintained by @vercel.
</HoverCardContent>
</HoverCard>;Anatomy
Examples
Composition
HoverCardContent composes the portal, the positioner and the popup, so a hover card is two parts
at the call site.
HoverCard
├── HoverCardTrigger
└── HoverCardContentTrigger Delays
delay and closeDelay on HoverCardTrigger set how long the pointer must rest before the card
opens, and how long it lingers after the pointer leaves. Base UI's defaults are 600ms and 300ms; the
close delay is what lets the pointer travel from the trigger into the card without dismissing it.
Positioning
side and align on HoverCardContent choose the edge the card opens from and where along that
edge it sits; sideOffset and alignOffset move it from there. Collision avoidance flips the card
when the requested side has no room, and the resolved placement is readable as data-side and
data-align.
Basic
A profile preview: handle, summary, and a muted footer line. The card is decoration over content the trigger already links to, so nothing inside it is the only route to that information.
Sides
All four physical sides, each with the same card.
RTL
Wrap the subtree in DirectionProvider direction="rtl" so Base UI resolves the logical sides.
side="inline-start" and side="inline-end" swap with the reading direction; left, right, top
and bottom stay where they are named. Pass dir to HoverCardContent as well — the card portals
out of the dir subtree and would otherwise render its text left-to-right.
API Reference
HoverCardContent adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
HoverCard and HoverCardTrigger add no props of their own — each accepts everything the Base UI
part accepts, including the trigger's delay and closeDelay.
Accessibility
- Renders Base UI's Preview Card. The trigger is a link, the card opens on hover and on keyboard focus, and Esc closes it.
- A hover card is an enhancement for sighted pointer users, not a delivery mechanism. Never put content in one that exists nowhere else, and never put a control in one that has no equivalent outside it.
- The card portals out of the trigger's subtree, so
HoverCardContentre-applies the theme scope on the positioner inside the portal (OVL-13) — a dark or scoped surface survives the portal. - Focus is the global 2px
:focus-visibleoutline frombase.css; there is no ring glow on the trigger or on the card. - Keep
closeDelaynon-zero so a pointer can cross the gap between the trigger and the card.
| Contract | States tested |
|---|---|
| Behaviour | default, open, closed |
| Accessibility | native-or-base-ui-semantics, browser-accessibility-test, keyboard-navigation |
| Visual | default, open, closed |
Do / Don't
Deviations
Upstream's file plus packages/ui/upstream/patches/hover-card.patch. Every hunk:
- OVL-13 —
HoverCardContentreadsuseInternalThemeScope()and attaches it to the Positioner INSIDE the portal.tooling/verify-portal-theme-scope.mjsinventories this host. - BRD-1 —
HoverCardContentdraws a real 1pxborder border-borderinstead of upstream'sring-1 ring-foreground/10box-shadow outline. A border survives forced colours and matches Elevation; nothing else about the surface moves. - DOC-2 —
import { cn } from "cn"becomes@vegastack/design; our npm layer ownscn. - DOC-1 — prettier reflow plus the
registry-stamp.mjsprovenance header the three-copy model needs; no decision of its own.