Overlays
HoverCard
A preview of where a link goes, shown when a mouse rests on the link or the keyboard focuses it: a cook's profile, a recipe's summary. It shows only on devices that can hover and never after a touch, so the link must work without it.
Install
Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):
npx shadcn@latest init --base base --preset nova --yes --no-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):
npx shadcn@latest add ourostack/ouro-ui/hover-card#v1.2.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/hover-cardIts tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):
npx shadcn@latest add ourostack/ouro-ui/hover-card#v1.2.13 ourostack/ouro-ui/hover-card-tests#v1.2.13import { HoverCard } from "@/components/ouro/hover-card/hover-card";Paths use shadcn's default @/ alias; the CLI rewrites them to yours.
Example
<HoverCard trigger={<Link href="/cooks/ari">Ari Mendelow</Link>}>
<CookSummary cook={ari} />
</HoverCard>Usage
It stacks above the page at --ouro-layer, a z-index of 50 by default (from ouro-base.css), like Dialog and Sheet.
Use instead when
- Use
Tooltipinstead for a short label, andPopoverfor anything the user needs or acts on, since a Popover opens on tap.
Props
Type: HoverCardProps.
align- Type:
"start" | "center" | "end" - Default:
"center" - How the card lines up with the link along that side.
children(required)- Type:
ReactNode - The preview: a few lines about where the link goes. Only what the user can do without; put controls elsewhere.
className- Type:
string - Layout only, applied to the card: width (18rem,
w-72, by default) and padding. Your classes win over Ouro UI's. Appearance classes warn in development. side- Type:
"top" | "bottom" | "left" | "right" - Default:
"bottom" - The side of the link the card sits on; it flips when there is no room.
trigger(required)- Type:
ReactElement - The link the card previews:
trigger={<Link href="/cooks/ari">Ari</Link>}. It works on its own: touch users, and devices that cannot hover, get only the link.
Don't use
These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.
closeDelay: Not a prop. Ouro UI sets the delays:<HoverCard trigger={…}>.content: Not a prop. The preview is the children:<HoverCard trigger={…}>…</HoverCard>.openDelay: Not a prop. Ouro UI sets the delays:<HoverCard trigger={…}>.placement: Not a prop. Ouro UI splits placement intosideandalign:<HoverCard side="bottom" align="start">.
className is for layout only
Pass margins, width and grid placement (mt-4, w-full, col-span-2). Your classes always win over Ouro UI's: HoverCard merges className last with cn (tailwind-merge), so a class that conflicts with its own replaces it.layout-classes
Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.
Alignment classes (text-center) warn too: use the align prop.
Claim sources
- layout-classes: test:registry/ouro/components/ouro/hover-card/hover-card.test.tsx::HoverCard > sits on the side and alignment it is given, and className sets layout
verify:testExists(registry/ouro/components/ouro/hover-card/hover-card.test.tsx::HoverCard > sits on the side and alignment it is given, and className sets layout)