Hover Card
A hover card previews where a link goes. Hover or focus the link and a small card shows who or what it points to, without leaving the page.
Built by @njoh-simplice with Base UI.
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
export default function HoverCardDemo() {
return (
<p className="max-w-sm text-center text-base">
Built by{" "}
<HoverCard>
<HoverCardTrigger href="https://github.com/njoh-simplice">@njoh-simplice</HoverCardTrigger>
<HoverCardContent>
<div className="flex gap-3">
<Avatar>
<AvatarImage
src="/assets/images/profile.webp"
alt=""
className="origin-[50%_38%] translate-y-[12%] scale-150 saturate-20"
/>
<AvatarFallback>NS</AvatarFallback>
</Avatar>
<div className="flex flex-col gap-1">
<p className="text-base font-semibold">Njoh Simplice Junior</p>
<p className="text-sm text-text/70">Builds nsdev/ui, a component library on Base UI and Tailwind.</p>
</div>
</div>
</HoverCardContent>
</HoverCard>{" "}
with Base UI.
</p>
);
}The code
The component is a single file, read from the repository when the site is built. Press Show code to see all of it.
import type { ComponentProps } from "react";
import { PreviewCard as BasePreviewCard } from "@base-ui/react/preview-card";
import { cx } from "@/lib/cx";
import { focusRing, popupMotion, popupSurface } from "@/lib/styles";
import type { WithClass } from "@/lib/types";
// A card that opens when you hover or focus a link, to preview where it goes. It is built on
// Base UI's PreviewCard.
export const HoverCard = BasePreviewCard.Root;
// A real link (an <a>), underlined. Give it an href.
export function HoverCardTrigger({ className, ...props }: WithClass<ComponentProps<typeof BasePreviewCard.Trigger>>) {
return (
<BasePreviewCard.Trigger
className={cx(
"rounded-sm text-text underline decoration-text-secondary underline-offset-4 transition-colors hover:decoration-text",
focusRing,
className,
)}
{...props}
/>
);
}
export function HoverCardContent({
side = "bottom",
sideOffset = 8,
align = "center",
className,
...props
}: WithClass<ComponentProps<typeof BasePreviewCard.Popup>> &
Pick<ComponentProps<typeof BasePreviewCard.Positioner>, "side" | "sideOffset" | "align">) {
return (
<BasePreviewCard.Portal>
<BasePreviewCard.Positioner side={side} sideOffset={sideOffset} align={align} className="z-50">
<BasePreviewCard.Popup className={cx(popupSurface, popupMotion, "w-72 p-4", className)} {...props} />
</BasePreviewCard.Positioner>
</BasePreviewCard.Portal>
);
}Props
| Prop | Type | Default | What it does |
|---|---|---|---|
href | string | — | On HoverCardTrigger. Where the link goes. It is a real link, so it works without the card. |
side | "top" | "right" | "bottom" | "left" | "bottom" | On HoverCardContent. Which side of the link it opens on. It flips to the other side if there is no room. |
align | "start" | "center" | "end" | "center" | On HoverCardContent. How it lines up along that side. |
delay | number | 600 | On HoverCardTrigger. Milliseconds to wait after hovering before it opens. |
closeDelay | number | 300 | On HoverCardTrigger. Milliseconds to wait after leaving before it closes. |
HoverCard is built on Base UI's PreviewCard. Every other prop goes to it.
The thinking
A hover card is a courtesy to people who want a quick look before they commit to a click. The page works exactly the same without it.
- Built on Base UI: It uses Base UI's PreviewCard, which opens the card on hover and focus, keeps it open while the pointer is over it, and places it next to the link.
- A real link: The trigger is an anchor with an href. If the card never opens, the link still goes where it should.
- Preview, not interface: The card is for reading. Anything people need to click, such as a button, belongs in a Popover.
Interface design
- Link: Underlined with a quiet line that turns to the main text color on hover, so it reads as a link in running text.
- Card: The shared overlay surface, 18rem wide, with no arrow. The demo shows a profile with the avatar.
Interaction
- Hover: The card opens after a short pause and stays open while the pointer moves from the link onto the card.
- Focus: It opens when the link receives keyboard focus, and closes when focus leaves.
- Click: Following the link works as usual.
- Motion: A 150ms fade and a small scale from the side it opens from. With reduced motion turned on it appears at once.
Empathy
- Nothing is lost without it: On touch screens there is no hover, so the card doesn't open. The link still works, so nothing the card says should be needed to use the page.
- Link text: The link has its own readable text, such as a name, so it makes sense out of context.
- Keyboard: Keyboard users get the card on focus, not only mouse users on hover.