Preview Card
A popup that appears when a link is hovered, showing a preview for sighted users. Built with Base UI and Tailwind CSS. Copy-paste ready.
import { CornerUpLeftIcon, StarIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
PreviewCard,
PreviewCardPopup,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
export default function Particle() {
return (
<PreviewCard>
<PreviewCardTrigger render={<Button variant="ghost" />}>
cnippet.dev/ui
</PreviewCardTrigger>
<PreviewCardPopup>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<h4 className="font-medium text-sm">ui.cnippet.dev</h4>
<p className="text-muted-foreground text-sm">
Beautifully designed components that you can copy and paste into
your apps.
</p>
</div>
<div className="flex items-center gap-4 text-muted-foreground text-xs">
<div className="flex items-center gap-1">
<span
aria-hidden="true"
className="size-2 rounded-full bg-blue-500"
/>
<span>TypeScript</span>
</div>
<div className="flex items-center gap-1">
<StarIcon className="size-3" />
<span>58.2k</span>
</div>
<div className="flex items-center gap-1">
<CornerUpLeftIcon className="size-3" />
<span>5.1k</span>
</div>
</div>
</div>
</PreviewCardPopup>
</PreviewCard>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/preview-card
Usage
import { Button } from "@/components/ui/button"
import {
PreviewCard,
PreviewCardPopup,
PreviewCardTrigger,
} from "@/components/ui/preview-card"<PreviewCard>
<PreviewCardTrigger>Open Preview Card</PreviewCardTrigger>
<PreviewCardPopup>Preview Card Content</PreviewCardPopup>
</PreviewCard>Notes
- Hover only — the popup appears on hover and is hidden from screen readers by design. Use it for visual enhancement, not critical information.
- Not a tooltip — tooltips are for short label text. Use
PreviewCardwhen you want to show a richer preview (image, description, metadata) before the user navigates. - Delay — Base UI adds a short hover delay by default to avoid flickering when the user mouses across the trigger.
Examples
User Mention Card
Hover over an @mention to see a profile card with avatar placeholder, display name, handle, bio, and follower/following counts.
Designed by @sarah_designs for the launch.
import { UserIcon } from "lucide-react";
import {
PreviewCard,
PreviewCardPopup,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
export function Pattern() {
return (
<p className="text-sm">
Designed by{" "}
<PreviewCard>
<PreviewCardTrigger className="cursor-pointer font-medium underline decoration-dashed underline-offset-4">
@sarah_designs
</PreviewCardTrigger>
<PreviewCardPopup>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3">
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted">
<UserIcon
aria-hidden="true"
className="size-5 text-muted-foreground"
/>
</div>
<div>
<p className="font-semibold text-sm">Sarah Kim</p>
<p className="text-muted-foreground text-xs">@sarah_designs</p>
</div>
</div>
<p className="text-muted-foreground text-sm">
Senior product designer. Open to work. Based in San Francisco.
</p>
<div className="flex gap-4 text-muted-foreground text-xs">
<span>
<strong className="text-foreground">2.4k</strong> followers
</span>
<span>
<strong className="text-foreground">312</strong> following
</span>
</div>
</div>
</PreviewCardPopup>
</PreviewCard>{" "}
for the launch.
</p>
);
}
Article Preview
Hover over a blog post link to reveal a thumbnail placeholder, title, excerpt, and read-time indicator before navigating.
Read more in Building accessible components with Base UI.
import { ClockIcon } from "lucide-react";
import {
PreviewCard,
PreviewCardPopup,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
export function Pattern() {
return (
<p className="text-sm">
Read more in{" "}
<PreviewCard>
<PreviewCardTrigger className="cursor-pointer font-medium text-primary underline underline-offset-4">
Building accessible components with Base UI
</PreviewCardTrigger>
<PreviewCardPopup>
<div className="flex flex-col gap-3">
<div className="h-28 w-full rounded-md bg-muted" />
<div>
<h4 className="font-semibold text-sm leading-snug">
Building accessible components with Base UI
</h4>
<p className="mt-1 line-clamp-3 text-muted-foreground text-xs">
Learn how to leverage Base UI primitives to build fully
accessible components with keyboard navigation, focus
management, and ARIA attributes without the heavy lifting.
</p>
</div>
<div className="flex items-center gap-1.5 text-muted-foreground text-xs">
<ClockIcon aria-hidden="true" className="size-3" />8 min read
</div>
</div>
</PreviewCardPopup>
</PreviewCard>
.
</p>
);
}
Product Card
Hover over a product name to see an image placeholder, star rating, review count, and price — common in recommendation and search result lists.
We recommend the Pro Wireless Headphones for remote work setups.
// biome-ignore-all lint/suspicious/noArrayIndexKey:<>
import { StarIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
PreviewCard,
PreviewCardPopup,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
export function Pattern() {
return (
<p className="text-sm">
We recommend the{" "}
<PreviewCard>
<PreviewCardTrigger className="cursor-pointer font-medium underline decoration-dashed underline-offset-4">
Pro Wireless Headphones
</PreviewCardTrigger>
<PreviewCardPopup>
<div className="flex flex-col gap-3">
<div className="h-32 w-full rounded-md bg-muted" />
<div>
<div className="flex items-start justify-between gap-2">
<h4 className="font-semibold text-sm">
Pro Wireless Headphones
</h4>
<Badge size="sm" variant="secondary">
New
</Badge>
</div>
<div className="mt-1 flex items-center gap-0.5 text-muted-foreground text-xs">
{Array.from({ length: 5 }).map((_, i) => (
<StarIcon
aria-hidden="true"
className={`size-3 ${i < 4 ? "fill-amber-400 text-amber-400" : "text-muted-foreground"}`}
key={i}
/>
))}
<span className="ml-1">(128 reviews)</span>
</div>
</div>
<p className="font-semibold text-base">$149.00</p>
</div>
</PreviewCardPopup>
</PreviewCard>{" "}
for remote work setups.
</p>
);
}
Issue Preview
Hover over a GitHub-style issue number to reveal the title, open/closed status icon, author, labels, and repository metadata.
Related to issue #1842 which is now resolved.
import { CircleDotIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
PreviewCard,
PreviewCardPopup,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
export function Pattern() {
return (
<p className="text-sm">
Related to issue{" "}
<PreviewCard>
<PreviewCardTrigger className="cursor-pointer font-medium font-mono text-primary underline underline-offset-4">
#1842
</PreviewCardTrigger>
<PreviewCardPopup>
<div className="flex flex-col gap-2.5">
<div className="flex items-start gap-2">
<CircleDotIcon
aria-hidden="true"
className="mt-0.5 size-4 shrink-0 text-green-500"
/>
<div>
<h4 className="font-semibold text-sm leading-snug">
PopoverContent loses focus when Slider is used inside
</h4>
<p className="mt-0.5 text-muted-foreground text-xs">
Opened by <strong>@deepak</strong> · 3 days ago
</p>
</div>
</div>
<div className="flex flex-wrap gap-1">
<Badge size="sm" variant="secondary">
bug
</Badge>
<Badge size="sm" variant="secondary">
accessibility
</Badge>
<Badge size="sm" variant="secondary">
popover
</Badge>
</div>
<p className="text-muted-foreground text-xs">
repo: ui-cnippet · labels: 3 · comments: 7
</p>
</div>
</PreviewCardPopup>
</PreviewCard>{" "}
which is now resolved.
</p>
);
}
Package Preview
Hover over an npm package name to see the package icon, version, description, weekly download count, and language tag.
Install @base-ui/react to get started with accessible primitives.
import { DownloadIcon, PackageIcon } from "lucide-react";
import {
PreviewCard,
PreviewCardPopup,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
export function Pattern() {
return (
<p className="text-sm">
Install{" "}
<PreviewCard>
<PreviewCardTrigger className="cursor-pointer font-medium font-mono underline underline-offset-4">
@base-ui/react
</PreviewCardTrigger>
<PreviewCardPopup>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2.5">
<div className="flex size-9 items-center justify-center rounded-lg bg-muted">
<PackageIcon
aria-hidden="true"
className="size-4 text-muted-foreground"
/>
</div>
<div>
<p className="font-semibold text-sm">@base-ui/react</p>
<p className="text-muted-foreground text-xs">v1.0.0</p>
</div>
</div>
<p className="text-muted-foreground text-xs">
Unstyled, accessible UI components for React built with
accessibility in mind.
</p>
<div className="flex items-center gap-3 text-muted-foreground text-xs">
<div className="flex items-center gap-1">
<DownloadIcon aria-hidden="true" className="size-3" />
<span>124k / week</span>
</div>
<div className="flex items-center gap-1">
<span
aria-hidden="true"
className="size-2 rounded-full bg-blue-500"
/>
<span>TypeScript</span>
</div>
</div>
</div>
</PreviewCardPopup>
</PreviewCard>{" "}
to get started with accessible primitives.
</p>
);
}

