HoverCard
A floating card that appears when a trigger is hovered or focused. Use for lightweight previews — user profiles, link previews, citations, or footnote definitions. Built on Base UI's preview-card primitive.
Playground
Installation
pnpm add @tessinaui/uiUsage
import {
HoverCard,
HoverCardTrigger,
HoverCardContent,
HoverCardHeader,
HoverCardTitle,
HoverCardDescription,
HoverCardBody,
HoverCardFooter,
HoverCardArrow,
} from "@tessinaui/ui";<HoverCard>
<HoverCardTrigger render={<button>@blaz</button>} />
<HoverCardContent side="bottom" align="start">
<HoverCardHeader leading={<Avatar initials="BZ" />}>
<HoverCardTitle>Blaz Zorcic</HoverCardTitle>
<HoverCardDescription>Designer · Building TessinaUI.</HoverCardDescription>
</HoverCardHeader>
<HoverCardFooter>
<Button variant="outline" size="xs">Message</Button>
<Button size="xs">Follow</Button>
</HoverCardFooter>
<HoverCardArrow />
</HoverCardContent>
</HoverCard>Showcase
When to use HoverCard vs. Tooltip vs. Popover
| Use HoverCard when… | Use Tooltip when… | Use Popover when… |
|---|---|---|
| The preview is rich (avatar, description, actions) | You only need short informational text | The user needs interactive controls (forms, lists) |
| Trigger is hover/focus (mouse + keyboard) | Trigger is hover/focus and content is non-interactive | Trigger is click |
| Touch users gracefully don't see the preview | Touch users gracefully don't see the tip | Works equally on touch + mouse |
| Examples: user profile, link preview, citation | Examples: button label, icon meaning | Examples: filter dropdown, rename input |
Touch behaviour. HoverCard is mouse/keyboard-only. On touch devices Base UI swallows the hover gesture so the trigger just behaves like a normal button. If you need an equivalent on touch, pair it with a
Popovertriggered by click, or surface the same content elsewhere.
Anatomy
Only HoverCard, HoverCardTrigger, and HoverCardContent are required. Everything else composes optionally inside HoverCardContent.
<HoverCard>
<HoverCardTrigger>…</HoverCardTrigger>
<HoverCardContent>
<HoverCardHeader>
<HoverCardTitle />
<HoverCardDescription />
</HoverCardHeader>
<HoverCardBody />
<HoverCardFooter />
<HoverCardArrow />
</HoverCardContent>
</HoverCard>Size
| Size | Width | Text |
|---|---|---|
"sm" | w-64 (16rem) | xs |
"md" (default) | w-72 (18rem) | sm |
"lg" | w-80 (20rem) | sm |
"xl" | w-96 (24rem) | base |
Intent
intent drives the optional border colour (when showIntentBorder is on) and the arrow's stroke. The popup keeps a neutral border by default — opt in with showIntentBorder for status previews.
| Value | Border with showIntentBorder |
|---|---|
"none" (default) | border-border |
"error" | border-error |
"warning" | border-warning |
"success" | border-success |
"info" | border-info |
Rounded
| Value | CSS |
|---|---|
"none" | rounded-none |
"sm" | rounded-md |
"md" (default) | rounded-lg |
"lg" | rounded-xl |
"full" | rounded-3xl |
Positioning
HoverCardContent accepts side (top | right | bottom | left), align (start | center | end), sideOffset (px gap from trigger, default 8), and alignOffset (px shift along the side, default 0).
<HoverCardContent side="right" align="start" sideOffset={12}>
…
</HoverCardContent>Delays
Pass delay (open) and closeDelay (close) on the root, in milliseconds. Defaults match Base UI: delay = 600, closeDelay = 300.
<HoverCard delay={300} closeDelay={150}>
…
</HoverCard>The values propagate to HoverCardTrigger via context — override per-trigger if you have multiple triggers in one card group.
Header slots
HoverCardHeader takes optional leading (avatar / icon) and trailing (badge / status) slots in addition to its title + description children.
<HoverCardHeader
leading={<Avatar initials="BZ" />}
trailing={<Badge variant="dot" color="success" />}
>
<HoverCardTitle>Blaz Zorcic</HoverCardTitle>
<HoverCardDescription>Online now</HoverCardDescription>
</HoverCardHeader>Matching button corners to the card
HoverCardFooter is a generic slot — buttons inside don't auto-inherit the card's rounded. Pass rounded explicitly on each button to keep its corners flowing into the card edge:
<HoverCard rounded="none">
<HoverCardTrigger render={<button>@blaz</button>} />
<HoverCardContent>
…
<HoverCardFooter>
{/* match the HoverCard's rounded prop */}
<Button variant="outline" size="xs" rounded="none">Message</Button>
<Button variant="primary" size="xs" rounded="none">Follow</Button>
</HoverCardFooter>
</HoverCardContent>
</HoverCard>Controlled
HoverCard accepts open, defaultOpen, and onOpenChange.
const [open, setOpen] = React.useState(false);
<HoverCard open={open} onOpenChange={setOpen}>
…
</HoverCard>Accessibility
- Built on
@base-ui/react/preview-card— opens on hover or keyboard focus of the trigger. - The card's content is announced when focus moves into it; otherwise it remains presentational while the trigger is hovered.
- Pressing Escape closes the card.
- On touch input the card does not open — the trigger is announced as a normal button.
- Use
intent="error"or"warning"withshowIntentBorderto give status previews a visual signal that complements text content.
RTL
Pass dir="rtl" to the root. Header alignment, the leading/trailing slots, and footer button order all flip to follow the reading direction.
<HoverCard dir="rtl">
<HoverCardTrigger render={<button>@blaz</button>} />
<HoverCardContent>
<HoverCardHeader>
<HoverCardTitle>بلاز زورتشيتش</HoverCardTitle>
<HoverCardDescription>مصمم · يبني TessinaUI.</HoverCardDescription>
</HoverCardHeader>
<HoverCardArrow />
</HoverCardContent>
</HoverCard>API Reference
HoverCard (root)
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "lg" | "xl" | "md" | Width + text size of the popup |
rounded | "none" | "sm" | "md" | "lg" | "full" | "md" | Corner radius |
intent | "none" | "error" | "warning" | "success" | "info" | "none" | Used by the optional intent border |
showIntentBorder | boolean | false | When true, the popup border picks up the intent colour |
dir | "ltr" | "rtl" | "ltr" | Reading direction |
delay | number | 600 (Base UI) | Open delay in ms (forwarded to Trigger) |
closeDelay | number | 300 (Base UI) | Close delay in ms (forwarded to Trigger) |
open | boolean | — | Controlled open state |
defaultOpen | boolean | false | Uncontrolled initial state |
onOpenChange | (open, event) => void | — | Fired when open state changes |
HoverCardContent
| Prop | Type | Default | Description |
|---|---|---|---|
side | "top" | "right" | "bottom" | "left" | "bottom" | Which side of the trigger to position on |
align | "start" | "center" | "end" | "center" | Alignment along the side axis |
sideOffset | number | 8 | Distance in px from the trigger |
alignOffset | number | 0 | Shift along the side axis |
portal | boolean | true | Render inside a portal |
container | HTMLElement | null | document.body | Portal target |
size, rounded, intent, showIntentBorder | — | inherited | Override the root's variant |
HoverCardHeader
| Prop | Type | Default | Description |
|---|---|---|---|
leading | ReactNode | — | Avatar or icon rendered beside the title |
trailing | ReactNode | — | Slot for badge, status dot, or follow button |
HoverCardTrigger
Renders a <button>. Use render={<a href="…" />} to swap in a link, or render={<MyComponent />} for any element. Accepts delay / closeDelay to override the values inherited from the root.
Sub-components
HoverCardTitle, HoverCardDescription, HoverCardBody, HoverCardFooter, HoverCardArrow, HoverCardBackdrop — all accept className and any HTML props.
Notes
- Built on
@base-ui/react/preview-card— Base UI's name for the same primitive Radix and shadcn call HoverCard. - Enter/exit animations use Base UI's
data-[starting-style]/data-[ending-style]plus position-awaredata-[side=*]attributes, so the popup "lifts off" from whichever side it opens on. - The arrow is drawn as a single rotated square — no SVG — so it inherits the surface colour and (when
showIntentBorderis on) the intent border colour automatically.
Popover
A floating panel that appears next to its trigger and holds interactive content. Click-activated, with full keyboard support, positioning, an optional arrow, and compound parts for header, body, and footer.
Container
A layout primitive that constrains content to a maximum width, applies consistent horizontal padding, and centers it within the viewport. Seven sizes, seven padding presets, polymorphic rendering, and RTL support.