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>
Loading...