The Preview Card component shows a popup with additional content when hovering over or focusing on a trigger element, similar to a rich tooltip or popover.
Installation
npx shadcn@latest add @eo-n/preview-card
Install dependencies
npm install @base-ui/react
Copy component code
Copy and paste the following code into your project at components/ui/preview-card.tsx:"use client";
import * as React from "react";
import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react";
import { cn } from "@/lib/utils";
const PreviewCard = PreviewCardPrimitive.Root;
function PreviewCardTrigger({
...props
}: React.ComponentProps<typeof PreviewCardPrimitive.Trigger>) {
return (
<PreviewCardPrimitive.Trigger data-slot="preview-card-trigger" {...props} />
);
}
function PreviewCardPortal({
...props
}: React.ComponentProps<typeof PreviewCardPrimitive.Portal>) {
return (
<PreviewCardPrimitive.Portal data-slot="preview-card-portal" {...props} />
);
}
function PreviewCardBackdrop({
...props
}: React.ComponentProps<typeof PreviewCardPrimitive.Backdrop>) {
return (
<PreviewCardPrimitive.Backdrop
data-slot="preview-card-backdrop"
{...props}
/>
);
}
interface PreviewCardContentProps
extends Omit<
React.ComponentProps<typeof PreviewCardPrimitive.Positioner>,
"render"
> {}
function PreviewCardContent({
className,
sideOffset = 4,
children,
...props
}: PreviewCardContentProps) {
return (
<PreviewCardPortal>
<PreviewCardBackdrop />
<PreviewCardPrimitive.Positioner
data-slot="preview-card-positioner"
sideOffset={sideOffset}
className="z-50 size-auto"
{...props}
>
<PreviewCardPrimitive.Popup
data-slot="preview-card-content"
className={cn(
"bg-popover text-popover-foreground w-64 max-w-[var(--available-width)] overflow-x-hidden overflow-y-auto rounded-md border p-4 shadow-md transition-[transform,scale,opacity] duration-150 ease-out",
"origin-[var(--transform-origin)] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
className
)}
>
{children}
</PreviewCardPrimitive.Popup>
</PreviewCardPrimitive.Positioner>
</PreviewCardPortal>
);
}
export {
PreviewCard,
PreviewCardTrigger,
PreviewCardPortal,
PreviewCardBackdrop,
PreviewCardContent,
};
Update imports
Update the import paths to match your project setup.
Usage
import {
PreviewCard,
PreviewCardContent,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
<PreviewCard>
<PreviewCardTrigger>Hover me</PreviewCardTrigger>
<PreviewCardContent>
This is the preview content.
</PreviewCardContent>
</PreviewCard>
Examples
Basic Preview Card
import {
PreviewCard,
PreviewCardContent,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
<PreviewCard>
<PreviewCardTrigger asChild>
<button>Hover to preview</button>
</PreviewCardTrigger>
<PreviewCardContent>
<p>Innovating in reverse.</p>
</PreviewCardContent>
</PreviewCard>
Rich Content Preview
import {
PreviewCard,
PreviewCardContent,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
<PreviewCard>
<PreviewCardTrigger asChild>
<a href="#" className="font-medium underline">
@username
</a>
</PreviewCardTrigger>
<PreviewCardContent>
<div className="flex items-start gap-3">
<Avatar>
<AvatarImage src="https://github.com/aeonzz.png" />
<AvatarFallback>UN</AvatarFallback>
</Avatar>
<div className="space-y-1">
<h4 className="text-sm font-semibold">Username</h4>
<p className="text-sm text-muted-foreground">
Full-stack developer and open source enthusiast.
</p>
<div className="flex gap-2 text-xs text-muted-foreground">
<span>1.2k followers</span>
<span>•</span>
<span>340 following</span>
</div>
</div>
</div>
</PreviewCardContent>
</PreviewCard>
Link Preview
import {
PreviewCard,
PreviewCardContent,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
<PreviewCard>
<PreviewCardTrigger asChild>
<a href="https://example.com" className="text-primary hover:underline">
View Documentation
</a>
</PreviewCardTrigger>
<PreviewCardContent className="w-80">
<div className="space-y-2">
<img
src="/docs-preview.png"
alt="Documentation preview"
className="w-full rounded-md"
/>
<h3 className="font-semibold">Getting Started Guide</h3>
<p className="text-sm text-muted-foreground">
Learn how to set up and use our platform in just a few minutes.
</p>
</div>
</PreviewCardContent>
</PreviewCard>
Product Preview
import {
PreviewCard,
PreviewCardContent,
PreviewCardTrigger,
} from "@/components/ui/preview-card";
import { Badge } from "@/components/ui/badge";
<PreviewCard>
<PreviewCardTrigger asChild>
<button className="text-left">
Premium Plan
</button>
</PreviewCardTrigger>
<PreviewCardContent>
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="font-semibold">Premium</h3>
<Badge>Popular</Badge>
</div>
<div className="text-2xl font-bold">$29/month</div>
<ul className="space-y-1 text-sm text-muted-foreground">
<li>✓ Unlimited projects</li>
<li>✓ Priority support</li>
<li>✓ Advanced analytics</li>
<li>✓ Custom domains</li>
</ul>
</div>
</PreviewCardContent>
</PreviewCard>
Custom Positioning
<PreviewCard>
<PreviewCardTrigger>Hover me</PreviewCardTrigger>
<PreviewCardContent
side="top"
sideOffset={8}
align="start"
>
This preview opens above the trigger.
</PreviewCardContent>
</PreviewCard>
Component API
PreviewCard
The root container component.
Default open state for uncontrolled usage.
Callback when the open state changes.
Delay in milliseconds before opening. Defaults to 600.
Delay in milliseconds before closing. Defaults to 300.
PreviewCardTrigger
The element that triggers the preview.
When true, merges props into the immediate child element instead of rendering a button.
PreviewCardContent
The preview content container.
side
'top' | 'right' | 'bottom' | 'left'
The preferred side of the trigger to render against.
Distance in pixels from the trigger. Defaults to 4.
align
'start' | 'center' | 'end'
The alignment of the preview relative to the trigger.
Offset in pixels from the alignment axis.
Elements to check for collision.
collisionPadding
number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>
Padding from the collision boundary.
Accessibility
The Preview Card component includes:
- Keyboard navigation support (opens on focus)
- Screen reader announcements
- Focus management
- ESC key to close
- Proper ARIA attributes
Reference
Built on top of Base UI Preview Card.