Image Gallery Layouts
A photo gallery is a small collision of several CSS problems at once: a responsive grid that reflows without media queries, images that must not distort regardless of their source aspect ratio, and — if you want a lightbox — a way to show one image full-size without leaving the page. None of it needs JavaScript-heavy libraries anymore.
The responsive grid
repeat(auto-fit, minmax(...)) (see repeat(), auto-fill & auto-fit) is the standard gallery grid: it fits as many columns as the container allows, and each column never shrinks below the minmax floor.
<div class="gallery"> <img src="a.jpg" alt="Description of photo A" /> <img src="b.jpg" alt="Description of photo B" /> <img src="c.jpg" alt="Description of photo C" /> <!-- ... --> </div>
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 0.75rem;
}
.gallery img {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
border-radius: 8px;
display: block;
}aspect-ratio: 1 / 1forces every tile to the same square shape regardless of the source image dimensions — no layout jump while images load.object-fit: cover(see object-fit) crops to fill that box instead of distorting or letterboxing.minmax(220px, 1fr)sets the smallest a tile can go before wrapping to fewer columns — tune it for your thumbnail size.
aspect-ratio or explicit width/height attributes on the <img> itself — both prevent cumulative layout shift while images are still downloading.Masonry-style with CSS columns
True masonry (items packed by shortest column, not fixed rows) has an experimental grid-template-rows: masonry in some browsers, but the reliably cross-browser approximation is multi-column layout (see Multi-Column Layout), which lets each image keep its natural aspect ratio.
.masonry {
columns: 4 220px; /* up to 4 columns, each at least 220px */
column-gap: 0.75rem;
}
.masonry img {
width: 100%;
display: block;
margin-bottom: 0.75rem;
border-radius: 8px;
break-inside: avoid; /* don't split an image across columns */
}Hover overlays
.tile {
position: relative;
overflow: hidden;
border-radius: 8px;
}
.tile .caption {
position: absolute;
inset: auto 0 0 0;
padding: 0.75rem;
background: linear-gradient(to top, rgb(0 0 0 / 0.7), transparent);
color: white;
transform: translateY(100%);
transition: transform 0.2s ease;
}
.tile:hover .caption,
.tile:focus-within .caption {
transform: translateY(0);
}:hover with :focus-within (see :focus-visible & :focus-within) so keyboard users tabbing to the link inside a tile see the same caption a mouse user gets on hover.Lazy loading
<img src="thumb.jpg" alt="Sunset over the harbor" loading="lazy" decoding="async" width="400" height="400" />
loading="lazy"is a native HTML attribute (not CSS) but belongs in every gallery — the browser defers off-screen image requests until they near the viewport.decoding="async"lets the browser decode the image off the main thread instead of blocking rendering.Always pair lazy loading with explicit
width/height(oraspect-ratioin CSS) so the browser can reserve space before the image arrives.
A CSS-only lightbox with <dialog>
For a full-size preview, wrap each thumbnail in a button that opens a <dialog> containing the large image (see Modal & Dialog Patterns for the full trapping/animation story).
<button class="tile" onclick="document.getElementById('lb-1').showModal()">
<img src="thumb.jpg" alt="Sunset over the harbor" />
</button>
<dialog id="lb-1" class="lightbox">
<img src="full.jpg" alt="Sunset over the harbor" />
<form method="dialog"><button aria-label="Close">✕</button></form>
</dialog>.lightbox {
border: none;
padding: 0;
background: transparent;
max-width: 90vw;
max-height: 90vh;
}
.lightbox img {
display: block;
max-width: 90vw;
max-height: 90vh;
object-fit: contain;
}
.lightbox::backdrop {
background: rgb(0 0 0 / 0.8);
}<dialog> gives you Escape-to-close and focus trapping automatically, so the "lightbox" needs no JavaScript beyond the one line that calls showModal().Grid vs columns vs flex for galleries
Approach | Best for |
|---|---|
Grid + | Uniform tiles, e-commerce thumbnails, predictable row alignment |
Multi-column ( | Pinterest-style masonry where natural image height is part of the design |
Flexbox + | Variable-width tiles (e.g. differing aspect ratios you want to keep) packed left to right |
Related pages: object-fit, aspect-ratio, Multi-Column Layout, Modal & Dialog Patterns, and repeat(), auto-fill & auto-fit.