Circle Menu
Dialog Form
Dominoes List Scroll
Dominoes Scroll Indicator
Eagle Vision
Electric AI Input
File Input
Flip Scroll
Glowing Scroll Indicator
Horizontal Scroll
Icon Wheel
Image Pile
Interactive CTA
Interactive Folder
Interest Picker
Jelly Loader
Leave Rating
Mask Cursor Effect
Magnet Tabs
Masonry Grid
OTP Input
Photo Gallery
Pixelated Carousel
Rolling Ball Scroll Indicator
Rubik Cube
Sidebar
Sine Wave
Skeumorphic Music Card
Social Media Card
Stacked Input Form
Stack Scroll
Trading Card
Social Media Card
A versatile card component that mimics the look and feel of popular social media platforms. Supports Twitter/X, Reddit, and Peerlist styles with authentic layouts, interaction buttons, and platform-specific design elements.
Install dependencies
npm i lucide-react
Utility function
Create a file lib/utils.ts and paste this code
import { ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Component
Create a file skeumorphic-music-card.tsx in your components folder and paste this code
import { cn } from '@/lib/utils';
import {
ArrowBigDown,
ArrowBigUp,
Award,
BadgeCheck,
Bookmark,
ChartNoAxesColumn,
Ellipsis,
Forward,
Heart,
MessageCircle,
RefreshCcw,
ShieldCheck,
Upload
} from 'lucide-react';
import Image from 'next/image';
type Post = {
name?: string;
comment?: string | React.ReactNode;
pfp?: string;
platform?: 'x' | 'peerlist' | 'reddit';
isVerified?: boolean;
username?: string;
image?: string;
};
const TwitterCard = ({
name,
pfp,
comment,
className,
isVerified = false,
username,
image
}: {
name: string;
pfp: string;
comment: string | React.ReactNode;
className?: string;
isVerified?: boolean;
username?: string;
image?: string;
}) => {
const commentsNumber = Math.floor(Math.random() * 10);
const retweetsNumber = Math.floor(Math.random() * 5);
const viewsNumber = Math.floor(Math.random() * 100);
const likesNumber = Math.floor(Math.random() * 5) + 1;
const iconData = [
{
icon: MessageCircle,
count: commentsNumber,
textColor: 'text-white/70',
iconProps: { size: 16, strokeWidth: 1 }
},
{
icon: RefreshCcw,
count: retweetsNumber,
textColor: 'text-white/70',
iconProps: { size: 16, strokeWidth: 1 }
},
{
icon: Heart,
count: likesNumber,
textColor: 'text-[#f81981]',
iconProps: { size: 16, strokeWidth: 1, fill: '#F81981', stroke: '#F81981' }
},
{
icon: ChartNoAxesColumn,
count: viewsNumber,
textColor: 'text-white/70',
iconProps: { size: 16, strokeWidth: 1 }
}
];
const actionIcons = [
{ icon: Bookmark, iconProps: { size: 16, strokeWidth: 1 } },
{ icon: Upload, iconProps: { size: 16, strokeWidth: 1 } }
];
return (
<div
className={cn(
'flex items-start justify-start border border-[#29292a] gap-2 sm:gap-3 bg-[#000000] px-2 sm:px-3 pt-2 sm:pt-3 pb-3 sm:pb-4 w-full rounded-lg relative pl-3 sm:pl-5',
className
)}
>
<Ellipsis
className="hidden sm:block absolute top-3 right-3 sm:top-4 sm:right-5"
size={14}
strokeWidth={1}
/>
{pfp && (
<Image
src={pfp}
alt={name || 'pfp'}
width={256}
height={256}
className="rounded-full h-[28px] w-[28px] sm:h-[32px] sm:w-[32px]"
/>
)}
<div className="flex flex-col items-start gap-0.5 sm:gap-1 justify-start w-full pr-1 sm:pr-2">
<div className="flex items-center gap-0.5">
<p className="font-bold leading-4 sm:leading-5 text-xs sm:text-base flex-1 whitespace-nowrap">
{name}
</p>
{isVerified && (
<BadgeCheck
size={16}
strokeWidth={1.5}
stroke="#000"
fill="#1D9BF0"
className="sm:w-5 sm:h-5"
/>
)}
{username && (
<p className="ml-0.5 leading-4 sm:leading-5 text-white/50 font-think text-xs sm:text-sm truncate line-clamp-1">
{'@'}
{username}
</p>
)}
</div>
<p className="text-xs sm:text-sm text-white">{comment}</p>
{image && (
<Image
src={image}
alt={name || 'image'}
width={500}
height={500}
className="rounded-lg mt-1 sm:mt-2 w-full h-auto"
/>
)}
<div className="flex items-center justify-between w-full mt-1 sm:mt-2">
{iconData.map((item, index) => {
const IconComponent = item.icon;
return (
<div key={index} className="flex items-center gap-0.5 sm:gap-1">
<IconComponent {...item.iconProps} className={item.textColor} size={16} />
<p className={`text-[10px] sm:text-xs ${item.textColor}`}>{item.count}</p>
</div>
);
})}
<div className="flex items-center gap-1 sm:gap-2">
{actionIcons.map((item, index) => {
const IconComponent = item.icon;
return (
<IconComponent
key={index}
{...item.iconProps}
className="text-white/70"
size={16}
/>
);
})}
</div>
</div>
</div>
</div>
);
};
const PeerlistCard = ({
name,
pfp,
comment,
className,
isVerified = false,
image
}: {
name: string;
pfp: string;
comment: string | React.ReactNode;
className?: string;
isVerified?: boolean;
image?: string;
}) => {
const likesNumber = Math.floor(Math.random() * 3) + 1;
return (
<div
className={cn(
'flex items-start h-fit justify-start border border-[#2a2a2a] gap-2 sm:gap-3 bg-[#171717] pt-2 sm:pt-3 pb-3 sm:pb-4 w-full rounded-lg relative px-3 sm:px-5',
className
)}
>
<div className="h-[28px] w-[28px] sm:h-[32px] sm:w-[32px] rounded-full relative">
{pfp && (
<Image
src={pfp}
alt={name || 'pfp'}
width={256}
height={256}
className="rounded-full shrink-0"
/>
)}
{isVerified && (
<ShieldCheck
size={14}
strokeWidth={1}
stroke="#fff"
fill="#23C45F"
className="absolute bottom-0 right-0 translate-x-0.5 translate-y-0.5"
/>
)}
</div>
<div className="flex flex-col items-start gap-0.5 justify-start w-full">
<p className="font-bold leading-4 sm:leading-5 text-sm sm:text-base">{name}</p>
<p className="text-xs sm:text-sm text-white/90">{comment}</p>
{image && (
<Image
src={image}
alt={name || 'image'}
width={500}
height={500}
className="rounded-lg mt-1 sm:mt-2 w-full h-auto"
/>
)}
<div className="flex items-center justify w-full gap-4 sm:gap-6 mt-1 sm:mt-2">
<p className="text-green-500 text-[9px] sm:text-[10px]">Liked • {likesNumber}</p>
<p className="text-[9px] sm:text-[10px] text-white/70">Reply</p>
<Ellipsis size={14} strokeWidth={1} />
</div>
</div>
</div>
);
};
const RedditCard = ({
name,
pfp,
comment,
className,
image
}: {
name: string;
pfp: string;
comment: string | React.ReactNode;
className?: string;
image?: string;
}) => {
const likesNumber = Math.floor(Math.random() * 3) + 1;
return (
<div
className={cn(
'flex items-start h-fit justify-start border border-[#2c2c2c] gap-2 sm:gap-3 bg-[#0C1416] pl-2 sm:pl-3 pr-4 sm:pr-6 pt-2 sm:pt-3 pb-3 sm:pb-4 w-full rounded-lg relative',
className
)}
>
{pfp && (
<Image
src={pfp}
alt={name || 'pfp'}
width={256}
height={256}
className="rounded-full shrink-0 h-[28px] w-[28px] sm:h-[32px] sm:w-[32px]"
/>
)}
<div className="flex flex-col items-start gap-1 sm:gap-2 justify-start w-full">
<p className="font-semibold text-xs sm:text-sm mt-1">{name}</p>
{image && (
<Image
src={image}
alt={name || 'image'}
width={500}
height={500}
className="rounded-lg mt-1 sm:mt-2 w-full max-w-[60%] h-auto"
/>
)}
<p className="text-xs sm:text-sm text-white/70">{comment}</p>
<div className="flex items-center text-white/60 font-medium justify w-full gap-3 sm:gap-6">
<div className="flex items-center justify-center gap-0.5 sm:gap-1">
<ArrowBigUp
size={20}
strokeWidth={1}
fill="#D83A02"
stroke="#D83A02"
className="sm:w-6 sm:h-6"
/>
<p className="text-[10px] sm:text-xs">{likesNumber}</p>
<ArrowBigDown size={20} strokeWidth={1} className="sm:w-6 sm:h-6" />
</div>
<div className="flex items-center justify-center gap-0.5 sm:gap-1">
<MessageCircle size={14} strokeWidth={1.4} />
<p className="text-[10px] sm:text-xs">Reply</p>
</div>
<div className="flex items-center justify-center gap-0.5 sm:gap-1">
<Award size={14} strokeWidth={1.4} />
<p className="text-[10px] sm:text-xs">Award</p>
</div>
<div className="flex items-center justify-center gap-0.5 sm:gap-1">
<Forward size={14} strokeWidth={1.4} />
<p className="text-[10px] sm:text-xs">Share</p>
</div>
<div className="flex items-center justify-center gap-0.5 sm:gap-1">
<Ellipsis size={14} strokeWidth={1.4} />
</div>
</div>
</div>
</div>
);
};
const SocialMediaCard = ({ post, className }: { post: Post; className?: string }) => {
if (!post) return null;
if (post.platform === 'x') {
return (
<TwitterCard
name={post.name || ''}
pfp={post.pfp || ''}
comment={post.comment || ''}
className={className}
isVerified={post.isVerified}
username={post.username || ''}
image={post.image || ''}
/>
);
}
if (post.platform === 'peerlist') {
return (
<PeerlistCard
name={post.name || ''}
pfp={post.pfp || ''}
comment={post.comment || ''}
className={className}
isVerified={post.isVerified}
image={post.image || ''}
/>
);
}
if (post.platform === 'reddit') {
return (
<RedditCard
name={post.name || ''}
pfp={post.pfp || ''}
comment={post.comment || ''}
className={className}
image={post.image || ''}
/>
);
}
return null;
};
export default SocialMediaCard;
Usage
<div className="h-full w-full flex flex-wrap items-center justify-center gap-2 relative">
<div className="flex flex-wrap max-w-[1020px] gap-4 items-center justify-center py-10">
<SocialMediaCard
post={{
name: 'Michael Thiessen',
isVerified: true,
username: 'MichaelThiessen',
comment: (
<>
The new if() selector in CSS lets you write conditional styles inline.
<br /> <br /> You can also switch based on custom properties:
</>
),
platform: 'x',
pfp: 'https://pbs.twimg.com/profile_images/1919844131137794049/rgavVVUV_400x400.jpg',
image: 'https://pbs.twimg.com/media/G1SIJiYXEAAPmOZ?format=jpg&name=medium'
}}
className="w-[300px] sm:w-[400px] md:w-[500px]"
/>
<SocialMediaCard
post={{
name: 'entropykimkc',
comment: 'mind blowing bro - how can i use it - can i use it?',
platform: 'reddit',
pfp: 'https://styles.redditmedia.com/t5_ewxcz5/styles/profileIcon_z2mn5yqe6gcf1.jpeg?width=64&height=64&frame=1&auto=webp&crop=64%3A64%2Csmart&s=fe5ea4f4d44683db192c5b12c980b6706553ac3a',
image:
'https://media1.giphy.com/media/v1.Y2lkPTZjMDliOTUybTZybTVyMTk2cDI3cXRhbXEyb3Q4anVjNmJjZnBvdDJ5ZTRoaThjNyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/JmCFfTlNy12wFpOK8Y/200.gif'
}}
className="w-[300px] sm:w-[400px] md:w-[500px]"
/>
<SocialMediaCard
post={{
name: 'Samit Kapoor',
comment: 'Today is a good day',
platform: 'peerlist',
pfp: 'https://pbs.twimg.com/profile_images/1964059042550091778/otqIGdFL_400x400.jpg',
isVerified: true,
image:
'https://dqy38fnwh4fqs.cloudfront.net/scroll/UHOK7LJRGLEOKR617M8NPNLQOO97-1758269508510'
}}
className="w-[300px] sm:w-[400px] md:w-[500px]"
/>
</div>
</div>