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
Masonry Grid
A grid layout that arranges items like Pinterest, automatically fitting them together without gaps. Perfect for image galleries and portfolios.
Install dependencies
npm i framer-motion tailwindcss-animateComponent
Create a file masonry-grid.tsx in your components folder and paste this code
'use client';
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import Image from 'next/image';
interface MasonryGridProps {
items: { image: string; title: string; description: string }[];
columns?: number | undefined;
}
const MasonryGrid: React.FC<MasonryGridProps> = ({ items, columns = undefined }) => {
const [imagesLoaded, setImagesLoaded] = useState<{ [key: string]: boolean }>({});
if (!items || items.length === 0) {
return <div className="text-center p-4">No items to display</div>;
}
return (
<div
style={{
columns: columns
}}
className={`${
!columns && 'columns-1 sm:columns-2 md:columns-3 lg:columns-4'
} gap-2 overflow-y-auto p-3 w-full`}
>
{items.map((item, index) => {
// Calculate row number based on screen size and index
const getColumnCount = () => {
if (typeof window === 'undefined') return 1;
const width = window.innerWidth;
if (width >= 1024) return 4; // lg
if (width >= 768) return 3; // md
if (width >= 640) return 2; // sm
return 1;
};
const columnCount = getColumnCount();
const rowIndex = Math.floor(index / columnCount);
return (
<motion.div
key={index}
className="break-inside-avoid mb-4 relative group rounded-xl overflow-hidden p-1 border-[1px] border-transparent hover:border-[1px] hover:border-neutral-800"
initial={{ opacity: 0, y: 20 }}
animate={{
opacity: 1,
y: 0,
transition: {
duration: 0.5,
delay: rowIndex * 0.1,
ease: 'easeOut'
}
}}
whileHover={{ scale: 1.05, rotateZ: 1 }}
>
<div className="relative">
<div className="relative w-full flex gap-1 flex-col items-start justify-start">
{!imagesLoaded[item.image] && (
<div className="absolute inset-0 w-full h-[300px] bg-neutral-500/50 animate-pulse rounded-lg" />
)}
<Image
src={item.image}
alt={item.title}
width={400}
height={300}
className={`${
!imagesLoaded[item.image] ? 'opacity-0' : 'opacity-100'
} w-full h-auto transition-transform duration-300 rounded-lg`}
sizes="(max-width: 640px) 100vw, (max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
onLoad={() => {
setImagesLoaded((prev) => ({ ...prev, [item.image]: true }));
}}
onError={() => {
console.error(`Error loading image: ${item.image}`);
setImagesLoaded((prev) => ({ ...prev, [item.image]: true }));
}}
/>
{imagesLoaded[item.image] && (
<div className="w-full">
<h3 className="text-sm font-medium">{item.title}</h3>
<p className="mt-0 text-xs text-neutral-500 line-clamp-2 overflow-hidden">
{item.description}
</p>
</div>
)}
</div>
</div>
</motion.div>
);
})}
</div>
);
};
export default MasonryGrid;
Usage
<MasonryGrid
items={[
{
title: 'Urban Skyline',
image:
'https://images.unsplash.com/photo-1718563552473-2d97b224e801?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw1fHx8ZW58MHx8fHx8',
description:
'A breathtaking view of a modern cityscape with towering skyscrapers illuminated at dusk.'
},
{
title: 'Mountain Retreat',
image:
'https://images.unsplash.com/photo-1735317461815-1a0ba64e9a56?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwyMXx8fGVufDB8fHx8fA%3D%3D',
description:
'A serene cabin nestled in the heart of towering mountains, perfect for a peaceful getaway.'
},
{
title: 'Forest Wander',
image:
'https://images.unsplash.com/photo-1502082553048-f009c37129b9?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
description:
'A misty trail winding through a dense, enchanting forest filled with lush greenery.'
},
{
title: 'Serene Lake',
image:
'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
description:
'A tranquil lake reflecting the golden hues of the sunset, surrounded by peaceful nature.'
},
{
title: 'Golden Hour',
image:
'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
description:
'A mesmerizing sunset casting a warm glow over the ocean, creating a dreamlike atmosphere.'
},
{
title: 'Coastal Vibes',
image:
'https://images.unsplash.com/photo-1493558103817-58b2924bce98?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
description:
'Crystal-clear waves crashing against a sandy shore, offering a perfect beach escape.'
},
{
title: 'Night Lights',
image:
'https://images.unsplash.com/photo-1502933691298-84fc14542831?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8d2F0ZXIlMjBzcG9ydHxlbnwwfHwwfHx8MA%3D%3D',
description:
'A dazzling city skyline at night, with vibrant lights illuminating the urban landscape.'
},
{
title: 'Rustic Charm',
image:
'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
description:
'A cozy wooden cabin with a warm, inviting atmosphere set in a countryside setting.'
}
]}
></MasonryGrid>