bx

Hover Card

For sighted users to preview content available behind a link.

@bext
tsx
<HoverCard>
  <HoverCardTrigger>
    <span className="underline underline-offset-4 cursor-pointer">@bext</span>
  </HoverCardTrigger>
  <HoverCardContent align="center">
    <div className="flex gap-3">
      <Avatar>
        <AvatarFallback>BX</AvatarFallback>
      </Avatar>
      <div className="space-y-1">
        <div className="text-sm font-semibold">@bext</div>
        <p className="text-sm text-muted-foreground">The framework for shipping fast websites. Built in Rust.</p>
      </div>
    </div>
  </HoverCardContent>
</HoverCard>

HoverCard is a floating card revealed on hover, for rich content (mini profile, link preview, contextual details). It is similar to Tooltip but wider (w-64 by default) and can hold images and links.

CSS-only pattern: the trigger and the content sit in a wrapper that carries the named group class group/hovercard (named so that it does not collide with another hover group higher up). The content goes from invisible opacity-0 to visible opacity-100 via group-hover/hovercard:, so the hover reveal needs no JavaScript. The ui.js island adds the keyboard: the card also opens when focus enters the HoverCard, and closes on blur and on Escape.

Use it for: a user mini profile when hovering an @username, a preview of an external link, a summary of a listed entity.

Installation

Terminal
$ bext ui add hover-card

Usage

tsx
import { HoverCard, HoverCardTrigger, HoverCardContent } from "@bext-stack/ui/primitives"

<HoverCard>
  <HoverCardTrigger>
    <a href="/user/@bext" className="underline">@bext</a>
  </HoverCardTrigger>
  <HoverCardContent align="center">
    {/* mini profile, image, bio... */}
  </HoverCardContent>
</HoverCard>

Anatomy

<HoverCard>                                // group/hovercard
  ├── <HoverCardTrigger>
  │   └── <a href="/user/@bext">@bext</a>
  └── <HoverCardContent align="center">   // invisible → visible on hover or focus
      └── ...rich content...

API Reference

HoverCard

Prop Type Default Description
className string — Extra classes on the wrapper, a with relative inline-block and the group/hovercard class.

HoverCardTrigger

Prop Type Default Description
children ReactNode — Element to hover or focus to reveal the card. When it contains no link or button, the island makes the trigger itself a tab stop.

HoverCardContent

Prop Type Default Description
align "start" | "center" | "end" "center" Horizontal alignment. start=left-0, center=left-1/2 -translate-x-1/2, end=right-0.
id string — Optional id on the card, for example to reference it with aria-describedby.
className string — Default: absolute top-full mt-2 w-64 rounded-md border bg-popover p-4 shadow-md.

Data Attributes

Attributes you can target in custom CSS to override the style for each state.

Attribute On Values Description
data-slot every sub-component "hover-card" | "hover-card-trigger" | "hover-card-content" Stable selector for custom styling, and the hooks the island listens on.
data-state HoverCard "open" | "closed" Set by the island when the card is open by hover or by focus.

Keyboard

Key Action
Tab Focusing the trigger reveals the card. Tab then moves through the links inside the card; the card closes when focus leaves the HoverCard.
Escape Hides the card. Focus stays on the trigger. The card shows again on the next focus or hover.

Accessibility

The card opens on hover and on keyboard focus, stays open while the pointer or the focus is inside it, and closes on blur, on pointer leave and on Escape. Prefer a link or a <button> inside HoverCardTrigger; when the trigger holds plain text, the island gives it tabindex="0" so it can still be focused.

The card has no role and is not linked to the trigger with ARIA: a screen reader reaches its content in reading order, after the trigger. It cannot be opened on a touch screen without a hover. For content the user must be able to open on demand, use Popover.

Use it only for non-essential content: the information must remain reachable without hover (e.g. the username stays clickable even without seeing the card).

Examples

Aligned start

Aligned start
tsx
<HoverCard>
  <HoverCardTrigger>
    <span className="underline cursor-pointer">Aligned start</span>
  </HoverCardTrigger>
  <HoverCardContent align="start">
    <p className="text-sm">align="start": flush with the left edge of the trigger.</p>
  </HoverCardContent>
</HoverCard>
Try it live
Open in play

Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom: fine-grained reactivity, no virtual DOM.

src/app/page.tsx 2 files · signals · runs in your browser
Pure PRISM + real bext signals: fine-grained, no re-render.