Popover Card

PreviousNext

A premium hover/focus popover for quick previews — users, files and extra details — with blur entrance, an animated arrow and staggered content.

Reviewed by @sofia and attached to the thread.

"use client";

import {
  Calendar,
  Download,
  FileText,
  MapPin,
  Star,
  UserPlus,
} from "lucide-react";

import {
  PopoverCard,
  PopoverCardBody,
  PopoverCardContent,
  PopoverCardDescription,
  PopoverCardFooter,
  PopoverCardHeader,
  PopoverCardItem,
  PopoverCardTitle,
  PopoverCardTrigger,
} from "@/components/matos-ui/popover-card";

export function PopoverCardDemo() {
  return (
    <div className="flex min-h-52 w-full max-w-md flex-col items-center justify-center gap-10">
      <p className="text-center text-muted-foreground text-sm leading-relaxed">
        Reviewed by{" "}
        <PopoverCard>
          <PopoverCardTrigger underline className="font-medium text-foreground">
            @sofia
          </PopoverCardTrigger>
          <PopoverCardContent side="top">
            <PopoverCardHeader>
              <span className="flex size-11 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-chart-1/30 to-chart-4/30 font-semibold text-foreground text-sm ring-1 ring-border">
                SR
              </span>
              <div className="min-w-0">
                <PopoverCardTitle>Sofia Ramos</PopoverCardTitle>
                <PopoverCardDescription>
                  Lead Product Designer
                </PopoverCardDescription>
              </div>
            </PopoverCardHeader>
            <PopoverCardBody>
              Crafting the design system and obsessing over micro-interactions.
            </PopoverCardBody>
            <PopoverCardItem className="flex flex-wrap gap-x-4 gap-y-1.5 text-muted-foreground text-xs">
              <span className="inline-flex items-center gap-1.5">
                <MapPin className="size-3.5" aria-hidden="true" />
                Lisbon, PT
              </span>
              <span className="inline-flex items-center gap-1.5">
                <Calendar className="size-3.5" aria-hidden="true" />
                Joined 2021
              </span>
            </PopoverCardItem>
            <PopoverCardFooter>
              <button
                type="button"
                className="inline-flex h-8 flex-1 items-center justify-center gap-1.5 rounded-lg bg-primary font-medium text-primary-foreground text-xs shadow-xs transition-[transform,background-color] duration-200 hover:bg-primary/90 active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                <UserPlus className="size-3.5" aria-hidden="true" />
                Follow
              </button>
              <button
                type="button"
                className="inline-flex h-8 items-center justify-center gap-1.5 rounded-lg border border-border bg-background px-3 font-medium text-foreground text-xs transition-colors duration-200 hover:bg-muted/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                Message
              </button>
            </PopoverCardFooter>
          </PopoverCardContent>
        </PopoverCard>{" "}
        and attached{" "}
        <PopoverCard>
          <PopoverCardTrigger
            render={
              <button
                type="button"
                className="inline-flex items-center gap-1.5 rounded-lg border border-border bg-muted/40 px-2 py-0.5 font-medium text-foreground text-xs"
              />
            }
          >
            <FileText className="size-3.5" aria-hidden="true" />
            report.pdf
          </PopoverCardTrigger>
          <PopoverCardContent size="sm">
            <PopoverCardHeader>
              <span className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-destructive/10 text-destructive ring-1 ring-destructive/20">
                <FileText className="size-5" aria-hidden="true" />
              </span>
              <div className="min-w-0">
                <PopoverCardTitle className="truncate">
                  report.pdf
                </PopoverCardTitle>
                <PopoverCardDescription>
                  2.4 MB · Updated today
                </PopoverCardDescription>
              </div>
            </PopoverCardHeader>
            <PopoverCardItem className="flex items-center gap-1.5 text-muted-foreground text-xs">
              <Star
                className="size-3.5 fill-chart-4 text-chart-4"
                aria-hidden="true"
              />
              Shared with 4 people
            </PopoverCardItem>
            <PopoverCardFooter>
              <button
                type="button"
                className="inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-lg border border-border bg-background font-medium text-foreground text-xs transition-[transform,background-color] duration-200 hover:bg-muted/60 active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                <Download className="size-3.5" aria-hidden="true" />
                Download
              </button>
            </PopoverCardFooter>
          </PopoverCardContent>
        </PopoverCard>{" "}
        to the thread.
      </p>
    </div>
  );
}

Installation

pnpm dlx shadcn@latest add https://matos-ui.com/r/popover-card.json

Usage

import {
  PopoverCard,
  PopoverCardTrigger,
  PopoverCardContent,
  PopoverCardHeader,
  PopoverCardTitle,
  PopoverCardDescription,
  PopoverCardBody,
  PopoverCardFooter,
  PopoverCardItem
} from '@/components/matos-ui/popover-card'
<PopoverCard>
  <PopoverCardTrigger underline>@sofia</PopoverCardTrigger>
  <PopoverCardContent side="top">
    <PopoverCardHeader>
      <PopoverCardTitle>Sofia Ramos</PopoverCardTitle>
      <PopoverCardDescription>Product Designer</PopoverCardDescription>
    </PopoverCardHeader>
    <PopoverCardBody>Crafting the design system.</PopoverCardBody>
  </PopoverCardContent>
</PopoverCard>

The card opens on hover and on keyboard focus, so it works for pointer and keyboard users alike. It is built on Base UI's Preview Card, so positioning, collision handling, portalling and focus management come for free.

When to use

Reach for the Popover Card to surface non-essential, contextual information without navigating away:

  • User preview — avatar, name, role and quick actions on a mention.
  • File preview — type, size and metadata on a file chip.
  • Quick details — definitions, specs or a summary behind a link.
  • Extra info — anything that enriches the current view on demand.

For information the user must act on, use a dialog or popover with a click trigger instead.

Examples

Custom trigger

The trigger renders an <a> by default. Use the render prop to turn any element (a button, an avatar) into the trigger.

<PopoverCardTrigger
  render={<button type="button" className="rounded-lg border px-2 py-0.5" />}
>
  report.pdf
</PopoverCardTrigger>

Placement & size

<PopoverCardContent side="right" align="start" size="lg" sideOffset={12}>
  ...
</PopoverCardContent>

Delays

delay and closeDelay live on the trigger (in milliseconds).

<PopoverCardTrigger delay={200} closeDelay={120}>
  Hover me
</PopoverCardTrigger>

Micro-interactions

  • Blur entrance — the popup fades in from a slight scale and blur, and blurs back out on close (driven by Base UI's starting/ending styles).
  • Animated arrow — the arrow rotates with a transition as the card flips to the opposite side on collision, and scales in with the popup.
  • Staggered contentHeader, Body, Footer and Item rise and de-blur one after another via Framer Motion.
  • Trigger lift — the trigger nudges up on hover and while open.
  • Reduced motion — all motion is disabled when the user prefers reduced motion.

Reference

PopoverCard

Wraps Base UI PreviewCard.Root. Accepts open, defaultOpen and onOpenChange for controlled usage.

PopoverCardTrigger

PropTypeDefaultDescription
renderelement<a>Render the trigger as a different element.
underlinebooleanfalseDotted underline styling for inline text triggers.
delaynumber600Time to wait before opening (ms).
closeDelaynumber300Time to wait before closing (ms).

PopoverCardContent

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Width of the card.
side"top" | "right" | "bottom" | "left""bottom"Preferred side relative to the trigger.
align"start" | "center" | "end""center"Alignment along the side.
sideOffsetnumber10Gap between trigger and card.
alignOffsetnumber0Offset along the alignment axis.
showArrowbooleantrueRender the animated arrow.

Layout subcomponents

ComponentDescription
PopoverCardHeaderStaggered row, ideal for an avatar + title block.
PopoverCardTitlePrimary label.
PopoverCardDescriptionMuted secondary label.
PopoverCardBodyStaggered body text block.
PopoverCardItemGeneric staggered row for custom content.
PopoverCardFooterStaggered footer with a top divider for actions.

Also exported: popoverCardTriggerVariants and popoverCardPopupVariants.

Best practices

  • Keep cards concise — they are a preview, not a full page.
  • Don't hide critical actions behind hover; mirror them elsewhere for touch users.
  • Provide accessible labels on icon-only action buttons inside the card.
  • Prefer side="top" for triggers near the bottom of the viewport.