A richer preview that opens when a link is hovered or reached with the keyboard, like a profile summary on a user name. It only repeats information that is available elsewhere.
Reviewed by Ada Lovelace on Friday.
import { Avatar, HoverCard, HoverCardContent, HoverCardTrigger } from "@rdloom/react";
export default function HoverCardBasicExample() {
return (
<div className="flex justify-center p-10">
<p className="text-sm">
Reviewed by{" "}
<HoverCard>
<HoverCardTrigger href="#ada-lovelace">Ada Lovelace</HoverCardTrigger>
<HoverCardContent label="Ada Lovelace, profile preview">
<div className="flex gap-3">
<Avatar name="Ada Lovelace" decorative />
<div className="flex flex-col gap-1">
<p className="font-semibold">Ada Lovelace</p>
<p className="text-[var(--rd-color-text-muted)]">Staff engineer, Platform</p>
<p className="text-[var(--rd-color-text-muted)]">Joined March 2021 · 48 reviews</p>
</div>
</div>
</HoverCardContent>
</HoverCard>{" "}
on Friday.
</p>
</div>
);
}Installation
npx rdloom add hover-cardCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/hover-card.jsonWorks, but without upgrade tracking.
Usage
import { Avatar, HoverCard, HoverCardContent, HoverCardTrigger } from "@rdloom/react";
<div className="flex justify-center p-10">
<p className="text-sm">
Reviewed by{" "}
<HoverCard>
<HoverCardTrigger href="#ada-lovelace">Ada Lovelace</HoverCardTrigger>
<HoverCardContent label="Ada Lovelace, profile preview">
<div className="flex gap-3">
<Avatar name="Ada Lovelace" decorative />
<div className="flex flex-col gap-1">
<p className="font-semibold">Ada Lovelace</p>
<p className="text-[var(--rd-color-text-muted)]">Staff engineer, Platform</p>
<p className="text-[var(--rd-color-text-muted)]">Joined March 2021 · 48 reviews</p>
</div>
</div>
</HoverCardContent>
</HoverCard>{" "}
on Friday.
</p>
</div>With links
Assigned to Grace Hopper.
import { Avatar, HoverCard, HoverCardContent, HoverCardTrigger } from "@rdloom/react";
// The same links exist on the profile page, so the card is a shortcut and never the only way in.
export default function HoverCardWithLinksExample() {
return (
<div className="flex justify-center p-10">
<p className="text-sm">
Assigned to{" "}
<HoverCard>
<HoverCardTrigger href="#grace-hopper">Grace Hopper</HoverCardTrigger>
<HoverCardContent label="Grace Hopper, profile preview">
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3">
<Avatar name="Grace Hopper" decorative />
<div>
<p className="font-semibold">Grace Hopper</p>
<p className="text-[var(--rd-color-text-muted)]">Compilers team</p>
</div>
</div>
<p className="text-[var(--rd-color-text-muted)]">Tab moves into the card; Esc closes it and keeps your place.</p>
<div className="flex gap-4">
<a href="#grace-profile" className="font-medium underline underline-offset-4 outline-none focus-visible:ring-2 focus-visible:ring-[var(--rd-color-focus-ring)]">
View profile
</a>
<a href="#grace-open-work" className="font-medium underline underline-offset-4 outline-none focus-visible:ring-2 focus-visible:ring-[var(--rd-color-focus-ring)]">
Open work
</a>
</div>
</div>
</HoverCardContent>
</HoverCard>
.
</p>
</div>
);
}Placement
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@rdloom/react";
export default function HoverCardPlacementExample() {
return (
<div className="flex flex-wrap justify-center gap-8 p-10 text-sm">
{(["top", "bottom", "start", "end"] as const).map((placement) => (
<HoverCard key={placement} placement={placement}>
<HoverCardTrigger href={`#${placement}`}>Opens {placement}</HoverCardTrigger>
<HoverCardContent label={`Preview placed ${placement}`}>
<p>This card prefers the {placement} side and flips when there is no room.</p>
</HoverCardContent>
</HoverCard>
))}
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
childrenrequiredA HoverCardTrigger (the link) followed by a HoverCardContent (the card). | node | none |
openDelayMilliseconds the pointer or keyboard focus waits on the trigger before the card opens. | number | 400 |
closeDelayMilliseconds before the card closes after the pointer or focus leaves the trigger and the card. Moving onto the card keeps it open. | number | 150 |
placementPreferred side of the trigger. Flips when there isn't room. | "top" | "bottom" | "start" | "end" | "bottom" |
isOpenControlled open state. | boolean | none |
defaultOpenInitial open state when uncontrolled. | boolean | none |
onOpenChangeCalled when the card opens or closes. | (isOpen: boolean) => void | none |
Accessibility
Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Tab onto the trigger opens the card after the open delay, without moving focus
- Tab again moves into the card when it has links or buttons; Tab past its last control continues to the next element and closes the card; Shift+Tab before its first control returns to the trigger
- Esc closes the card and keeps focus on the trigger
- The trigger keeps its own action: Enter follows the link
Screen readers announce
- The trigger is read as a normal link; once the card is open its text is read as the link's description
- A labelled group is announced when focus moves into the card
What your code must do
- The card never holds the only copy of anything: the same information must be reachable from the link's destination or the page
- Touch does nothing special: a tap follows the link, and no long press is needed
- The card stays open while the pointer is over it, so its links can be reached
- While open, the card is the description of the trigger (aria-describedby)
- Opening never moves focus; the card is not modal and the page behind stays available
Guidelines
Use it when
- A preview of something a link points to: a user, a repository, a product
- Extra context that is also on the linked page
Avoid it when
- The information is needed to complete a task: put it on the page
- Plain hints: use Tooltip
- Content with forms or long reading: use Popover or a page
Don't
- Hiding the only way to reach an action inside the card
- Relying on hover alone: the trigger must also work with focus and touch (the link itself)
- Putting a card on a control that is not a link
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-surface-raised--rd-color-border-default--rd-color-text-default--rd-color-text-muted--rd-color-action-primary--rd-color-focus-ring--rd-radius-overlay