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-animate
Component
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>