Light

Hover Card

For sighted users to preview content available behind a link.

Spec · from metadata

When to use

  • User profile preview cards on avatar/name hover
  • Link previews showing destination content
  • Entity summaries (project, document, etc.) on hover
  • Any read-only preview content triggered by hover

When not to use

  • Interactive content (forms, buttons, inputs) -- use Popover instead (click-triggered)
  • Simple text labels -- use Tooltip instead
  • Action menus -- use DropdownMenu instead
  • Content that must be accessible on touch devices -- HoverCard is hover-only

Anti-patterns

Avoid<HoverCard>
  <HoverCardTrigger>Info</HoverCardTrigger>
  <HoverCardContent>
    <form>
      <Input />
      <Button>Submit</Button>
    </form>
  </HoverCardContent>
</HoverCard>
Prefer<Popover>
  <PopoverTrigger asChild>
    <Button>Info</Button>
  </PopoverTrigger>
  <PopoverContent>
    <form>
      <Input />
      <Button>Submit</Button>
    </form>
  </PopoverContent>
</Popover>

HoverCard closes when the mouse leaves, making forms unreliable. Popover is click-triggered and stays open until explicitly dismissed.

Avoid<HoverCard>
  <HoverCardTrigger>
    <Button>Save</Button>
  </HoverCardTrigger>
  <HoverCardContent>Saves the document</HoverCardContent>
</HoverCard>
Prefer<Tooltip>
  <TooltipTrigger asChild>
    <Button>Save</Button>
  </TooltipTrigger>
  <TooltipContent>Saves the document</TooltipContent>
</Tooltip>

Simple text labels should use Tooltip, which has proper tooltip ARIA semantics and is lighter weight than HoverCard.

Accessibility

  • Screen readerContent should be supplementary -- do not put essential information only in HoverCard since it's inaccessible on touch devices and to keyboard-only users.
  • ContrastUses bg-card/text-card-foreground token pair for proper contrast.

Token bindings

TokenCategoryUsage
bg-cardcolorCard background
text-card-foregroundcolorCard text
shadow-mdshadowCard elevation
rounded-mdradiusCard border radius
p-4spacingContent padding

Import

import {
  HoverCard,
  HoverCardTrigger,
  HoverCardContent,
} from "@timelycare/helix-ui"

Props

interface HoverCardProps {
  open?: boolean
  onOpenChange?: (open: boolean) => void
  openDelay?: number  // default 700ms
  closeDelay?: number // default 300ms
  children: React.ReactNode
}

interface HoverCardContentProps {
  side?: "top" | "right" | "bottom" | "left"
  align?: "start" | "center" | "end"
  sideOffset?: number
  className?: string
  children: React.ReactNode
}

Trigger States

StateText StyleDecoration
Defaulttext-primary font-semiboldNone
Hovertext-primary font-semiboldunderline
Focustext-primary font-semiboldFocus ring (3px spread)
Disabledtext-primary font-semibold opacity-50None, pointer-events-none
Pressedtext-primary font-semiboldNone

Card Appearance

PropertyValue
Backgroundbg-popover
Borderborder border-border
Shadowshadow-md
Radiusrounded-md (8px)
Widthw-80 (320px)
Paddingp-4 (16px)

Content Structure

ElementSpecification
Layoutflex gap-4 horizontal
Avatar48×48px (size-12), rounded-full
Text wrapperflex flex-col gap-1
Titletext-sm font-semibold
Descriptiontext-sm
Meta rowpt-2, flex items-center gap-2
Meta iconsize-4 opacity-70
Meta texttext-xs text-muted-foreground

Common Patterns

User Profile Card

<HoverCard>
  <HoverCardTrigger asChild>
    <a href="/user" className="text-primary font-semibold hover:underline">
      @nextjs
    </a>
  </HoverCardTrigger>
  <HoverCardContent className="w-80">
    <div className="flex gap-4">
      <Avatar className="size-12">
        <AvatarImage src="/avatar.jpg" />
        <AvatarFallback>NX</AvatarFallback>
      </Avatar>
      <div className="flex-1 space-y-1">
        <h4 className="text-sm font-semibold">@nextjs</h4>
        <p className="text-sm">
          The React Framework - created and maintained by @vercel.
        </p>
        <div className="flex items-center gap-2 pt-2">
          <CalendarDays className="size-4 opacity-70" />
          <span className="text-xs text-muted-foreground">
            Joined December 2024
          </span>
        </div>
      </div>
    </div>
  </HoverCardContent>
</HoverCard>

Link Preview

<HoverCard>
  <HoverCardTrigger asChild>
    <a href={url} className="text-primary font-semibold hover:underline">
      {linkText}
    </a>
  </HoverCardTrigger>
  <HoverCardContent side="top" className="w-80">
    <div className="space-y-2">
      <h4 className="text-sm font-semibold">{title}</h4>
      <p className="text-sm text-muted-foreground">{description}</p>
    </div>
  </HoverCardContent>
</HoverCard>

Controlled Hover Card

const [open, setOpen] = useState(false)

<HoverCard open={open} onOpenChange={setOpen}>
  <HoverCardTrigger>Hover me</HoverCardTrigger>
  <HoverCardContent>{/* content */}</HoverCardContent>
</HoverCard>

Inline Trigger

<p className="text-sm">
  Created by{" "}
  <HoverCard>
    <HoverCardTrigger asChild>
      <span className="text-primary font-semibold cursor-pointer hover:underline">
        @vercel
      </span>
    </HoverCardTrigger>
    <HoverCardContent>{/* profile content */}</HoverCardContent>
  </HoverCard>
  {" "}team.
</p>

Accessibility

  • Built on Radix HoverCard
  • Appears on hover and focus (keyboard accessible via trigger focus)
  • Keyboard: Escape to dismiss
  • Screen reader: content accessible when trigger is focused
  • Not supported on touch devices — provide alternative access to content

Gotchas

ProblemSolution
Card appears too fastIncrease openDelay prop (default 700ms)
Card closes too fastIncrease closeDelay prop (default 300ms)
Trigger not workingAdd asChild prop when wrapping custom elements
Card position wrongAdjust side and align props
Touch devicesHoverCard doesn't work on touch — use Popover instead
No underline on defaultUnderline only appears on hover, not default state
Trigger text not styledUse text-primary font-semibold for trigger styling

See Also

  • Related Components: Tooltip (simpler hover info), Popover (click-triggered)
  • Tokens: Colors — Theme-aware color tokens

Last updated: February 9, 2026