HoverCard
When to use
Section titled “When to use”- A profile behind an @mention or an author’s name
- A summary of a linked repository, issue or document, before following the link
Not for anything people need: a hover card never opens on touch, so on a phone it doesn’t exist. Put essential content on the page, or in a Popover opened by a press.
Choosing between messages and popups:
| Component | Use it for | For example |
|---|---|---|
| Dialog | Something that needs a decision or input before the user carries on; the page is blocked meanwhile | Confirm a delete, edit a record in a form |
| Drawer | A panel that slides in from an edge and blocks the page; the first choice on a phone | Share options, a mobile nav, a filter panel |
| Popover | Extra content or controls next to a button, while the rest of the page stays usable | Filter panel, share options, details card |
| Menu | A list of actions or settings opened from a button | “More actions ⋯”, a View menu |
| Tooltip | A short text label on hover or focus; nothing in it can be clicked | Name of an icon button, a keyboard shortcut |
| HoverCard | A preview of where a link goes, on hover or focus; nothing essential in it | A profile behind an @mention, a repository’s summary |
| Toast | A brief confirmation after an action, which goes away by itself | “Saved”, “Archived — Undo” |
| Alert | A message that stays in the page until the situation changes | “Your trial ends in 3 days”, a form error summary |
Reviewed by @ada
AL
import { Avatar, HoverCard, Link, Paragraph, Stack, Text } from '@arun-dev/ui';
const photo = 'data:image/svg+xml,' + encodeURIComponent( '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect width="40" height="40" fill="#7c3aed"/><circle cx="20" cy="16" r="7" fill="#ede9fe"/><rect x="8" y="26" width="24" height="14" rx="7" fill="#ede9fe"/></svg>', );
export default function HoverCardBasics() { return ( <Paragraph> Reviewed by{' '} <HoverCard.Root> {/* The trigger is a real link: a tap follows it, and the card only adds detail. */} <HoverCard.Trigger render={<Link href="#ada" />}>@ada</HoverCard.Trigger> <HoverCard.Popup> <Stack direction="row" gap="sm" align="start"> <Avatar.Root> <Avatar.Image src={photo} alt="" /> <Avatar.Fallback>AL</Avatar.Fallback> </Avatar.Root> <Stack gap="3xs"> <Text weight="semibold">Ada Lovelace</Text> <Text color="secondary">Analyst. Writes the notes everyone else reads.</Text> <Link href="#ada-repos">12 repositories</Link> </Stack> </Stack> </HoverCard.Popup> </HoverCard.Root>{' '} on 4 October. </Paragraph> );}Anatomy
Section titled “Anatomy”import { HoverCard, Link } from '@arun-dev/ui';
<HoverCard.Root> <HoverCard.Trigger render={<Link href="/ada" />}>@ada</HoverCard.Trigger> <HoverCard.Popup /></HoverCard.Root>;| Part | Renders | Does |
|---|---|---|
Root | nothing | holds open, and the hover timers |
Trigger | <a> | the link it previews; the card is anchored to it |
Popup | <div popover="manual"> | the card |
Trigger is an unstyled link: render a Link to style it, or your
router’s link.
Opening and closing
Section titled “Opening and closing”| Input | Opens | Closes |
|---|---|---|
| Mouse | after resting on the link for delay (600ms) | closeDelay (300ms) after leaving both the link and the card |
| Keyboard | at once, on focus | when focus leaves both the link and the card, or on Esc |
| Touch | never: a tap follows the link | — |
- The pointer can move from the link onto the card: it stays open while either is hovered.
- After the link, Tab moves into the card, wherever the card is in the page, and on through its links. Esc closes it and puts focus back on the link.
- Pressing the link closes the card, since following the link is what you came to do.
- It doesn’t close an open Popover or Dialog, and Esc closes only the card.
Playground
Section titled “Playground”Hover or focus me
Card content
<HoverCard.Popup
side="bottom"
align="center"
>
Card content
</HoverCard.Popup>Accessibility
Section titled “Accessibility”- The link must make sense without the card. The card adds detail; it doesn’t replace the link’s text.
- Nothing in it may be the only way to do something: a touch user never sees it.
- It has no role, so it isn’t announced as a dialog. Screen reader users reach its content by tabbing on from the link.
HoverCard.Root
Section titled “HoverCard.Root”| Prop | Type | Default | Description |
|---|---|---|---|
delay | number | 600 | Milliseconds a pointer rests on the Trigger before the card opens. Keyboard focus opens it at once. |
closeDelay | number | 300 | Milliseconds between the pointer leaving the Trigger or the card and the card closing — time to cross the gap onto the card. |
open | boolean | Controlled state. Provide onOpenChange alongside it. Never pass undefined — the mode is fixed at mount. | |
defaultOpen | boolean | false | Initial state when uncontrolled. Read once, at mount. |
onOpenChange | (open: boolean) => void | Called on every open and close, after any delay has run. | |
children | ReactNode | The other parts. Root renders no element of its own. |
HoverCard.Popup
Section titled “HoverCard.Popup”| Prop | Type | Default | Description |
|---|---|---|---|
side | 'top' | 'bottom' | 'left' | 'right' | 'bottom' | Which side of the Trigger to show on. Flips to the opposite side when there is no room. |
align | 'start' | 'center' | 'end' | 'center' | Flush with the Trigger's start or end edge, or centred on it. |
className | string | Concatenated with the component's own classes, never replacing them. | |
render | ReactElement | Element or component to render instead of the default. Props, className, event handlers and ref are merged onto it. | |
ref | Ref<HTMLElement> | Ref to the rendered element. Merged with any ref on the render element. | |
…rest | HTMLAttributes | Anything else is spread onto the rendered element, so id, aria-*, data-* and event handlers all reach the DOM. |
HoverCard.Trigger takes the props of an <a>, plus render and ref.
- Restyle with the
--hover-card-*tokens, such as--hover-card-widthor--hover-card-padding. - Placement is CSS anchor positioning, as for Popover: it flips to the other side when there’s no room.
- Types:
import type { HoverCardRootProps } from '@arun-dev/headless/hover-card', orComponentProps<typeof HoverCard.Root>.