An icon, title, and body card whose icon gives a small animated response on hover.
npx shadcn@latest add @paragon/feature-cardimport * as React from "react";
import { BarChart3 } from "lucide-react";
import { cn } from "@/lib/utils";
export interface FeatureCardProps extends React.ComponentProps<"div"> {
/** Icon for the tile — any lucide icon or small SVG. Sized to 20px. */
icon?: React.ReactNode;
/** Feature name, rendered as the card heading. */
title?: string;
/** Supporting body copy under the title. */
description?: React.ReactNode;
/** Disables the hover response where the motion would distract. */
static?: boolean;
}
/**
* An icon, title, and body card. The motion budget is spent on exactly one
* element: on fine-pointer hover the icon tile lifts 2px on an enumerated
* transform transition (150ms, ease-out) while the card itself stays still —
* only its depth border deepens via shadow-border → shadow-border-hover.
* The nudge is gated behind (hover: hover) and (pointer: fine) and removed
* entirely under prefers-reduced-motion.
*/
export function FeatureCard({
icon = <BarChart3 />,
title = "Real-time analytics",
description = "Query live product data and share dashboards with your team without waiting on nightly syncs.",
static: isStatic = false,
className,
children,
...props
}: FeatureCardProps) {
return (
<div
data-slot="feature-card"
className={cn(
"group/feature-card flex flex-col items-start gap-4 rounded-xl bg-card p-5 text-card-foreground shadow-border transition-[box-shadow] duration-(--duration-quick) ease-(--ease-out)",
!isStatic && "hover:shadow-border-hover",
className,
)}
{...props}
>
<div
aria-hidden
className={cn(
"flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg]:size-5 [&_svg]:shrink-0",
!isStatic && [
"transition-transform duration-(--duration-quick) ease-(--ease-out)",
"motion-safe:[@media(hover:hover)_and_(pointer:fine)]:group-hover/feature-card:-translate-y-0.5",
],
)}
>
{icon}
</div>
<div className="flex flex-col gap-1">
<h3 className="text-sm font-medium">{title}</h3>
{description && (
<p className="text-sm text-muted-foreground">{description}</p>
)}
</div>
{children}
</div>
);
}