Components
Card
A flexible container for content fragments, with support for headers, footers, and variants.Copy Markdown
Choose from three distinct visual styles to perfectly match your UI's aesthetic.
A sophisticated design with ultra-rounded corners, subtle gradients, and a lifting hover interaction.
Premium design featuring deep backdrop-blur, edge-light effects, and a responsive glow that appears on hover.
Bold, high-contrast design with offset shadows and sharp edges. Features a snappy "push" interaction on hover.
The fastest way to get started is using the shadcn CLI.
Before using the @nurav-ui alias, add this to your components.json:
{
"registries": {
"@nurav-ui": "https://nurav-ui.vercel.app/r"
}
}
Or skip alias setup entirely and use the direct URL below.
npx shadcn@latest add @nurav-ui/card
Alternatively, install via direct URL (no components.json changes needed):
npx shadcn@latest add https://nurav-ui.vercel.app/r/card.json
If you prefer manual configuration, follow these steps:
Install Dependencies
Install the necessary animation and utility libraries:
npm install motion clsx tailwind-merge
Create/Customize Component
Copy the following code into your project (e.g., components/nurav-ui/Card.tsx). You can modify this code to create your own custom card designs:
'use client';
import React from 'react';
import { motion, Variants } from 'motion/react';
import { cn } from "@/lib/utils";
interface CardProps {
title?: React.ReactNode;
description?: React.ReactNode;
content?: React.ReactNode;
footer?: React.ReactNode;
variant?: 'v1' | 'v2' | 'v3';
className?: string;
headerClassName?: string;
contentClassName?: string;
footerClassName?: string;
onClick?: () => void;
}
export const Card = ({
title,
description,
content,
footer,
variant = 'v1',
className,
headerClassName,
contentClassName,
footerClassName,
onClick,
}: CardProps) => {
const containerVariants: Variants = {
initial: { opacity: 0, y: 10 },
animate: { opacity: 1, y: 0 },
hover: {
y: -8,
transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] },
},
tap: {
scale: 0.98,
},
};
const getVariantStyles = () => {
switch (variant) {
case 'v1':
return cn(
"rounded-[2.5rem] border border-foreground/[0.08] bg-gradient-to-br from-background via-background/95 to-background/90",
"shadow-[0_8px_30px_rgb(0,0,0,0.04)] dark:shadow-[0_8px_30px_rgb(0,0,0,0.1)]",
"hover:border-foreground/20 hover:shadow-[0_20px_50px_rgba(0,0,0,0.1)] transition-colors transition-shadow duration-500"
);
case 'v2':
return cn(
"rounded-3xl border border-white/10 bg-white/[0.03] backdrop-blur-xl",
"before:absolute before:inset-0 before:-z-10 before:rounded-3xl before:bg-gradient-to-br before:from-white/10 before:via-transparent before:to-transparent before:opacity-0 hover:before:opacity-100 before:transition-opacity",
"shadow-2xl hover:shadow-white/[0.05] transition-shadow duration-500 relative overflow-hidden"
);
case 'v3':
return cn(
"rounded-none border-[3px] border-foreground bg-background shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] dark:shadow-[8px_8px_0px_0px_rgba(255,255,255,1)]",
"hover:shadow-[12px_12px_0px_0px_rgba(0,0,0,1)] dark:hover:shadow-[12px_12px_0px_0px_rgba(255,255,255,1)]",
"transition-shadow duration-200"
);
default:
return "";
}
};
return (
<motion.div
initial="initial"
animate="animate"
whileHover="hover"
whileTap="tap"
variants={containerVariants}
onClick={onClick}
className={cn(
"group relative flex flex-col w-full max-w-sm transition-colors duration-500",
getVariantStyles(),
onClick && "cursor-pointer",
className
)}
>
{/* Decorative Glow for V1 & V2 */}
{(variant === 'v1' || variant === 'v2') && (
<div className="absolute -inset-px rounded-[inherit] opacity-0 group-hover:opacity-100 group-active:opacity-100 transition-opacity duration-500 pointer-events-none bg-gradient-to-br from-primary/20 via-transparent to-primary/5 -z-10" />
)}
{/* Header */}
{(title || description) && (
<div className={cn("px-8 py-7 flex flex-col gap-2", headerClassName)}>
{title && (
<h3 className={cn(
"text-2xl font-bold tracking-[ -0.02em] text-foreground leading-tight transition-colors duration-300",
variant === 'v3' && "uppercase text-3xl italic font-black",
"group-hover:text-primary group-active:text-primary transition-colors"
)}>
{title}
</h3>
)}
{description && (
<p className={cn(
"text-sm text-muted-foreground/80 leading-relaxed font-medium transition-all duration-300",
variant === 'v3' && "text-foreground font-bold"
)}>
{description}
</p>
)}
</div>
)}
{/* Content */}
<div className={cn(
"px-8 py-5 flex-1 text-base text-foreground/80 leading-relaxed",
(title || description) && "pt-0",
contentClassName
)}>
{content || (
<div className="h-32 w-full bg-foreground/[0.02] dark:bg-white/[0.02] rounded-2xl flex items-center justify-center border border-dashed border-foreground/10 group-hover:border-primary/30 group-active:border-primary/30 transition-colors duration-500 overflow-hidden relative">
<span className="text-muted-foreground/30 text-sm font-medium italic relative z-10">Empty Card Content</span>
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-transparent opacity-0 group-hover:opacity-100 group-active:opacity-100 transition-opacity duration-700" />
</div>
)}
</div>
{/* Footer */}
{footer && (
<div className={cn(
"px-8 py-6 mt-auto border-t border-foreground/[0.05] bg-foreground/[0.01]",
variant === 'v3' && "border-t-[3px] border-foreground bg-transparent py-4",
footerClassName
)}>
{footer}
</div>
)}
</motion.div>
);
};