Masonry Image Hover Grid
A CSS-columns masonry gallery with a scale + caption reveal on hover.
DOM + CSS1 dep1 file · 1 KB TSX
Props
This entry takes no configurable props.
"use client";
import { motion } from "framer-motion";
type Tile = { src: string; label: string };
export function MasonryImageHoverGrid({ tiles, columns = 3 }: { tiles: Tile[]; columns?: number }) {
return (
<div className={`columns-${columns} gap-2.5`} style={{ columnCount: columns }}>
{tiles.map((tile) => (
<motion.div
key={tile.src}
tabIndex={0}
whileHover="hover"
whileFocus="hover"
className="group relative mb-2.5 overflow-hidden rounded-xl break-inside-avoid outline-none focus-visible:ring-2 focus-visible:ring-red-500"
>
<motion.img
src={tile.src}
alt={tile.label}
variants={{ hover: { scale: 1.08 } }}
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
className="w-full"
/>
<motion.div
variants={{ hover: { opacity: 1, y: 0 } }}
initial={{ opacity: 0, y: 8 }}
transition={{ duration: 0.2 }}
className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/60 to-transparent px-3 py-2"
>
<span className="text-sm font-semibold text-white">{tile.label}</span>
</motion.div>
</motion.div>
))}
</div>
);
}
Free. Pro unlocks everything, from $27/month.
Details
About this asset
A true masonry layout needs items of different heights to stack without gaps, and CSS grid can't do that without precomputing row-spans; CSS multi-column (columns-N) does it natively, with break-inside-avoid keeping each tile from splitting across a column. The hover choreography — image scale-up and caption slide-fade together — runs off Framer's variant propagation: the parent sets whileHover="hover" once, and every child that declares a matching hover variant animates in sync, no per-child hover listener duplicating the trigger. Reach for CSS columns over grid when images have real, varying aspect ratios you don't want to precompute; reach for grid instead when the design needs strict row-aligned reading order, because column layout fills top-to-bottom within each column first, not left-to-right across a row — so the visual order for someone scanning left-to-right won't match the source array order the way a row-based grid would. The component ships with no default tiles; a consumer supplies the array and picks the column count.
Curator’s note
Uses CSS columns rather than CSS grid for the masonry flow — grid requires manually computing row-spans to avoid gaps between variable-height items, while columns lets the browser flow items into balanced columns natively.

