Card
Ultra-modern card component with hover effects, multiple variants, customizable layouts, and interactive features for displaying content.
Card Variants
Default Card
This is a default card with standard styling.
Elevated Card
This card has an elevated shadow effect.
Outlined Card
This card uses an outlined border style.
Filled Card
This card has a filled background.
Gradient Card
This card features a beautiful gradient background.
Glow Card
This card has a glowing effect.
Card Sizes
Small Card
Compact card size.
Medium Card
Standard card size.
Large Card
Larger card with more padding.
Extra Large Card
Maximum card size.
Cards with Images
Mountain View
Beautiful mountain landscape with snow-capped peaks.
Ocean Sunset
Stunning sunset over the ocean waves.
Forest Path
Peaceful forest trail surrounded by trees.
Cards with Icons
User Profile
Manage your account settings and preferences.
Calendar Events
View and manage your upcoming events.
Analytics Dashboard
Track your performance metrics and insights.
Interactive Cards
Interactive Card 1
This is card number 1 with interactive features.
Interactive Card 2
This is card number 2 with interactive features.
Interactive Card 3
This is card number 3 with interactive features.
Social Media Cards
John Doe
Just finished an amazing project! 🚀 The new design system is looking incredible. Can't wait to share more details soon.
Project Showcase
Check out this beautiful dashboard design I created for our latest client. The attention to detail and user experience really shines through! ✨
Product Cards
Premium Sneakers
Comfortable and stylish sneakers perfect for everyday wear.
Wireless Headphones
High-quality wireless headphones with noise cancellation.
Smart Watch
Feature-rich smartwatch with health tracking capabilities.
Loading States
Advanced Layouts
Dashboard Overview
Comprehensive overview of your application's performance and metrics.
Custom Content Card
Installation
Install the packages
npm i motion clsx tailwind-merge lucide-reactAdd util file
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Copy and paste the following code into your project
"use client";
import { motion, AnimatePresence } from "motion/react";
import React, { useState } from "react";
import { cn } from "@/lib/utils";
import { Loader2 } from "lucide-react";
interface CardProps {
title?: string;
description?: string;
image?: string;
icon?: React.ReactNode;
variant?: "default" | "elevated" | "outlined" | "filled" | "gradient";
size?: "sm" | "md" | "lg" | "xl";
hover?: boolean;
interactive?: boolean;
glow?: boolean;
badge?: string;
badgeColor?: "primary" | "secondary" | "success" | "warning" | "error";
footer?: React.ReactNode;
loading?: boolean;
onClick?: () => void;
className?: string;
children?: React.ReactNode;
}
const Card: React.FC<CardProps> = ({
title,
description,
image,
icon,
variant = "default",
size = "md",
hover = true,
interactive = false,
glow = false,
badge,
badgeColor = "primary",
footer,
loading = false,
onClick,
className,
children,
}) => {
const [isHovered, setIsHovered] = useState(false);
const sizeClasses = {
sm: "p-4",
md: "p-6",
lg: "p-8",
xl: "p-10",
};
const variantClasses = {
default: "bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700",
elevated: "bg-white border border-gray-200 shadow-lg dark:bg-gray-800 dark:border-gray-700",
outlined: "bg-transparent border-2 border-gray-300 dark:border-gray-600",
filled: "bg-gray-50 border border-gray-200 dark:bg-gray-800 dark:border-gray-700",
gradient: "bg-gradient-to-br from-blue-50 to-purple-50 border border-blue-200 dark:from-blue-950/20 dark:to-purple-950/20 dark:border-blue-800",
};
const badgeColorClasses = {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
success: "bg-green-500 text-white",
warning: "bg-yellow-500 text-black",
error: "bg-red-500 text-white",
};
const handleClick = () => {
if (interactive && onClick && !loading) {
onClick();
}
};
return (
<motion.div
className={cn(
"relative rounded-xl overflow-hidden transition-all duration-300",
variantClasses[variant],
sizeClasses[size],
interactive && "cursor-pointer",
hover && "hover:shadow-xl",
glow && "shadow-lg shadow-blue-500/25",
className
)}
onClick={handleClick}
onHoverStart={() => setIsHovered(true)}
onHoverEnd={() => setIsHovered(false)}
animate={{
scale: interactive && isHovered ? 1.02 : 1,
y: interactive && isHovered ? -4 : 0,
boxShadow: glow
? "0 0 20px rgba(59, 130, 246, 0.3)"
: interactive && isHovered
? "0 20px 40px rgba(0, 0, 0, 0.1)"
: "0 0 0 0px rgba(0, 0, 0, 0)",
}}
transition={{ duration: 0.3 }}
whileHover={hover ? { y: -2 } : {}}
whileTap={interactive ? { scale: 0.98 } : {}}
>
{/* Badge */}
<AnimatePresence>
{badge && (
<motion.div
className={cn(
"absolute top-4 right-4 px-2 py-1 text-xs font-medium rounded-full z-10",
badgeColorClasses[badgeColor]
)}
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
>
{badge}
</motion.div>
)}
</AnimatePresence>
{/* Loading Overlay */}
<AnimatePresence>
{loading && (
<motion.div
className="absolute inset-0 bg-white/80 dark:bg-gray-800/80 flex items-center justify-center z-20"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
>
<Loader2 className="w-6 h-6 text-blue-500" />
</motion.div>
</motion.div>
)}
</AnimatePresence>
{/* Image Header */}
<AnimatePresence>
{image && (
<motion.div
className="relative -m-6 mb-6 overflow-hidden"
initial={{ opacity: 0, scale: 1.1 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 1.1 }}
transition={{ duration: 0.3 }}
>
<motion.img
src={image}
alt={title || "Card image"}
className="w-full h-48 object-cover"
animate={{
scale: isHovered ? 1.05 : 1,
}}
transition={{ duration: 0.3 }}
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
</motion.div>
)}
</AnimatePresence>
{/* Header with Icon */}
{(icon || title) && (
<motion.div
className="flex items-center gap-3 mb-4"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<AnimatePresence>
{icon && (
<motion.div
className="flex-shrink-0"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
exit={{ scale: 0 }}
transition={{ duration: 0.2, delay: 0.2 }}
>
{icon}
</motion.div>
)}
</AnimatePresence>
<AnimatePresence>
{title && (
<motion.h3
className="text-lg font-semibold text-gray-900 dark:text-white"
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -10 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
{title}
</motion.h3>
)}
</AnimatePresence>
</motion.div>
)}
{/* Description */}
<AnimatePresence>
{description && (
<motion.p
className="text-gray-600 dark:text-gray-400 mb-4 leading-relaxed"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3, delay: 0.3 }}
>
{description}
</motion.p>
)}
</AnimatePresence>
{/* Main Content */}
<AnimatePresence>
{children && (
<motion.div
className="mb-4"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
{children}
</motion.div>
)}
</AnimatePresence>
{/* Footer */}
<AnimatePresence>
{footer && (
<motion.div
className="pt-4 border-t border-gray-200 dark:border-gray-700"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3, delay: 0.5 }}
>
{footer}
</motion.div>
)}
</AnimatePresence>
{/* Hover Effect Overlay */}
<AnimatePresence>
{interactive && isHovered && (
<motion.div
className="absolute inset-0 bg-blue-500/5 dark:bg-blue-400/5 pointer-events-none"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
/>
)}
</AnimatePresence>
{/* Interactive Indicator */}
<AnimatePresence>
{interactive && (
<motion.div
className="absolute bottom-4 right-4 w-2 h-2 bg-blue-500 rounded-full"
animate={{
scale: isHovered ? 1.2 : 1,
opacity: isHovered ? 1 : 0.5,
}}
transition={{ duration: 0.2 }}
/>
)}
</AnimatePresence>
</motion.div>
);
};
export default Card;"use client";
import { motion, AnimatePresence } from "motion/react";
import React, { useState } from "react";
import { cn } from "@/lib/utils";
import { Loader2 } from "lucide-react";
interface CardProps {
title?: string;
description?: string;
image?: string;
icon?: React.ReactNode;
variant?: "default" | "elevated" | "outlined" | "filled" | "gradient";
size?: "sm" | "md" | "lg" | "xl";
hover?: boolean;
interactive?: boolean;
glow?: boolean;
badge?: string;
badgeColor?: "primary" | "secondary" | "success" | "warning" | "error";
footer?: React.ReactNode;
loading?: boolean;
onClick?: () => void;
className?: string;
children?: React.ReactNode;
}
const Card: React.FC<CardProps> = ({
title,
description,
image,
icon,
variant = "default",
size = "md",
hover = true,
interactive = false,
glow = false,
badge,
badgeColor = "primary",
footer,
loading = false,
onClick,
className,
children,
}) => {
const [isHovered, setIsHovered] = useState(false);
const sizeClasses = {
sm: "p-4",
md: "p-6",
lg: "p-8",
xl: "p-10",
};
const variantClasses = {
default: "bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700",
elevated: "bg-white border border-gray-200 shadow-lg dark:bg-gray-800 dark:border-gray-700",
outlined: "bg-transparent border-2 border-gray-300 dark:border-gray-600",
filled: "bg-gray-50 border border-gray-200 dark:bg-gray-800 dark:border-gray-700",
gradient: "bg-gradient-to-br from-blue-50 to-purple-50 border border-blue-200 dark:from-blue-950/20 dark:to-purple-950/20 dark:border-blue-800",
};
const badgeColorClasses = {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
success: "bg-green-500 text-white",
warning: "bg-yellow-500 text-black",
error: "bg-red-500 text-white",
};
const handleClick = () => {
if (interactive && onClick && !loading) {
onClick();
}
};
return (
<motion.div
className={cn(
"relative rounded-xl overflow-hidden transition-all duration-300",
variantClasses[variant],
sizeClasses[size],
interactive && "cursor-pointer",
hover && "hover:shadow-xl",
glow && "shadow-lg shadow-blue-500/25",
className
)}
onClick={handleClick}
onHoverStart={() => setIsHovered(true)}
onHoverEnd={() => setIsHovered(false)}
animate={{
scale: interactive && isHovered ? 1.02 : 1,
y: interactive && isHovered ? -4 : 0,
boxShadow: glow
? "0 0 20px rgba(59, 130, 246, 0.3)"
: interactive && isHovered
? "0 20px 40px rgba(0, 0, 0, 0.1)"
: "0 0 0 0px rgba(0, 0, 0, 0)",
}}
transition={{ duration: 0.3 }}
whileHover={hover ? { y: -2 } : {}}
whileTap={interactive ? { scale: 0.98 } : {}}
>
{/* Badge */}
<AnimatePresence>
{badge && (
<motion.div
className={cn(
"absolute top-4 right-4 px-2 py-1 text-xs font-medium rounded-full z-10",
badgeColorClasses[badgeColor]
)}
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
>
{badge}
</motion.div>
)}
</AnimatePresence>
{/* Loading Overlay */}
<AnimatePresence>
{loading && (
<motion.div
className="absolute inset-0 bg-white/80 dark:bg-gray-800/80 flex items-center justify-center z-20"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
>
<Loader2 className="w-6 h-6 text-blue-500" />
</motion.div>
</motion.div>
)}
</AnimatePresence>
{/* Image Header */}
<AnimatePresence>
{image && (
<motion.div
className="relative -m-6 mb-6 overflow-hidden"
initial={{ opacity: 0, scale: 1.1 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 1.1 }}
transition={{ duration: 0.3 }}
>
<motion.img
src={image}
alt={title || "Card image"}
className="w-full h-48 object-cover"
animate={{
scale: isHovered ? 1.05 : 1,
}}
transition={{ duration: 0.3 }}
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
</motion.div>
)}
</AnimatePresence>
{/* Header with Icon */}
{(icon || title) && (
<motion.div
className="flex items-center gap-3 mb-4"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<AnimatePresence>
{icon && (
<motion.div
className="flex-shrink-0"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
exit={{ scale: 0 }}
transition={{ duration: 0.2, delay: 0.2 }}
>
{icon}
</motion.div>
)}
</AnimatePresence>
<AnimatePresence>
{title && (
<motion.h3
className="text-lg font-semibold text-gray-900 dark:text-white"
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -10 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
{title}
</motion.h3>
)}
</AnimatePresence>
</motion.div>
)}
{/* Description */}
<AnimatePresence>
{description && (
<motion.p
className="text-gray-600 dark:text-gray-400 mb-4 leading-relaxed"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3, delay: 0.3 }}
>
{description}
</motion.p>
)}
</AnimatePresence>
{/* Main Content */}
<AnimatePresence>
{children && (
<motion.div
className="mb-4"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3, delay: 0.4 }}
>
{children}
</motion.div>
)}
</AnimatePresence>
{/* Footer */}
<AnimatePresence>
{footer && (
<motion.div
className="pt-4 border-t border-gray-200 dark:border-gray-700"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3, delay: 0.5 }}
>
{footer}
</motion.div>
)}
</AnimatePresence>
{/* Hover Effect Overlay */}
<AnimatePresence>
{interactive && isHovered && (
<motion.div
className="absolute inset-0 bg-blue-500/5 dark:bg-blue-400/5 pointer-events-none"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
/>
)}
</AnimatePresence>
{/* Interactive Indicator */}
<AnimatePresence>
{interactive && (
<motion.div
className="absolute bottom-4 right-4 w-2 h-2 bg-blue-500 rounded-full"
animate={{
scale: isHovered ? 1.2 : 1,
opacity: isHovered ? 1 : 0.5,
}}
transition={{ duration: 0.2 }}
/>
)}
</AnimatePresence>
</motion.div>
);
};
export default Card;Update the import paths to match your project setup
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | undefined | Card title text. |
| description | string | undefined | Card description text. |
| image | string | undefined | Image URL for card header. |
| icon | ReactNode | undefined | Icon component for card header. |
| variant | 'default' | 'elevated' | 'outlined' | 'filled' | 'gradient' | 'default' | Visual style variant of the card. |
| size | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Size of the card. |
| hover | boolean | true | Enable hover effects. |
| interactive | boolean | false | Make card clickable with enhanced interactions. |
| glow | boolean | false | Add glow effect to the card. |
| badge | string | undefined | Badge text to display on card. |
| badgeColor | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'primary' | Color of the badge. |
| footer | ReactNode | undefined | Footer content for the card. |
| loading | boolean | false | Show loading state. |
| onClick | () => void | undefined | Click handler for interactive cards. |