How to Build a Responsive Image Gallery with HTML and CSS
Build a responsive image gallery with CSS Grid, consistent crops, responsive images, accessibility, lazy loading, and practical troubleshooting.
Direct answer: Put the images in a CSS Grid whose columns use repeat(auto-fit, minmax(...)), then give every image a deliberate frame with aspect-ratio, object-fit, and object-position. Use srcset and sizes when the same image is available at multiple resolutions, and use picture when the crop or composition should change. Include intrinsic dimensions, useful alt text, and lazy loading for images below the first viewport.
1. A complete responsive gallery
The following file runs as-is. Replace the example filenames with your own images and keep the intrinsic dimensions accurate.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Responsive image gallery</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.5;
color: #17202a;
background: #f5f7f9;
}
main { width: min(100% - 2rem, 72rem); margin: 2rem auto; }
h1 { margin-bottom: 1.5rem; }
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
gap: 1rem;
}
.gallery figure { margin: 0; }
.gallery img {
display: block;
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
object-position: center;
border-radius: .5rem;
background: #dfe5eb;
}
figcaption { margin-top: .4rem; font-size: .9rem; }
</style>
</head>
<body>
<main>
<h1>Lake trips</h1>
<div class="gallery">
<figure>
<img src="photo-1-800.jpg"
srcset="photo-1-400.jpg 400w, photo-1-800.jpg 800w, photo-1-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 33vw"
width="1200" height="900"
loading="lazy"
alt="A kayaker crossing a calm lake">
<figcaption>Morning crossing</figcaption>
</figure>
<figure>
<img src="photo-2-800.jpg" width="1200" height="900" loading="lazy" alt="Pine trees reflected in still water">
<figcaption>Still water</figcaption>
</figure>
<figure>
<img src="photo-3-800.jpg" width="1200" height="900" loading="lazy" alt="A wooden dock extending into a blue lake">
<figcaption>The dock</figcaption>
</figure>
<figure>
<img src="photo-4-800.jpg" width="1200" height="900" loading="lazy" alt="Hikers walking beside a mountain lake">
<figcaption>Trail beside the lake</figcaption>
</figure>
</div>
</main>
</body>
</html>
2. How the responsive grid works
auto-fit creates as many columns as fit. minmax(min(100%, 15rem), 1fr) prevents a card from becoming narrower than 15rem while also allowing a single card to fit a narrow phone. The 1fr tracks share the remaining width, so the columns expand and contract without a long list of breakpoint rules.
| Strategy | Use it when | Trade-off |
|---|---|---|
auto-fit with minmax() |
You want the column count to follow available width | Exact column counts are not guaranteed at every width |
| Fixed columns at media queries | The design requires exact desktop, tablet, and mobile layouts | More breakpoints to maintain |
auto-fill |
You want empty grid tracks to preserve their space | Visible gaps can remain when few items exist |
Preventing overflow
Use min(100%, 15rem) in the minimum track size. Without it, a minimum wider than the viewport can create horizontal scrolling. Also check long captions, borders, and custom margins when diagnosing overflow.
3. Keeping thumbnails the same size
Set the frame with aspect-ratio, then make the image fill it:
.gallery img {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
object-position: center;
}
cover fills the frame and crops overflow. Use contain when every pixel must remain visible; it can leave empty bands. Move the crop with values such as object-position: 50% 20%. Do not force one ratio on diagrams, screenshots, or product images where cropping removes meaning.
4. Responsive image files: srcset, sizes, and picture
Grid CSS controls arrangement; responsive image markup controls which file is downloaded. For the same composition at different resolutions, provide width candidates:
<img src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 33vw"
width="1200" height="900"
alt="A kayaker crossing a calm lake">
sizes must describe the rendered width, including the gallery’s gaps and container. If the image is one column on phones, two on tablets, and three on wide screens, the example above is a useful starting point; adjust it to your actual CSS.
Use picture for art direction, such as a portrait crop on mobile and a wide crop on desktop:
<picture>
<source media="(max-width: 600px)" srcset="lake-portrait-600.jpg">
<source media="(min-width: 601px)" srcset="lake-wide-1200.jpg">
<img src="lake-wide-1200.jpg" width="1200" height="800" alt="A mountain lake at sunrise">
</picture>
Always keep the img fallback with src and alt. MDN documents the distinction between resolution switching with srcset/sizes and art direction with picture in its responsive images guide.
5. Accessibility and layout stability
- Write concise
alttext describing the image’s relevant content. Usealt=""for purely decorative images. - Include accurate
widthandheightattributes so the browser reserves space before bytes arrive. - Use captions or nearby text for information that should not be hidden in an image.
- If gallery items are links, make the link target and focus state visible; do not rely on hover alone.
MDN describes alt as the textual replacement read by screen readers; see the img reference.
6. Lazy loading and loading priorities
loading="lazy" is appropriate for images below the initial viewport. Keep dimensions set on lazy images to reserve their space. The first visible image usually should load eagerly; adding lazy loading to it can delay the initial display. If you have a single prominent image, consider fetchpriority="high" only when it is genuinely the page’s key image.
7. Variations for common gallery designs
Fixed four-column desktop gallery
.gallery { display: grid; gap: 1rem; grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .gallery { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 550px) { .gallery { grid-template-columns: 1fr; } }
Unequal masonry-like rows without JavaScript
CSS Grid with a shared ratio produces predictable rows. A true masonry layout has different trade-offs and browser support considerations; for a reliable gallery, use consistent frames or a dedicated masonry approach only when preserving each image’s natural height is essential.
Preserving logos and screenshots
.gallery .uncropped { object-fit: contain; padding: .75rem; background: white; }
8. Troubleshooting checklist
| Symptom | Cause | Fix |
|---|---|---|
| Images stretch | Width and height are forced without a fitting rule | Set object-fit: cover or contain and define the frame ratio |
| Different thumbnail heights | No shared frame | Add aspect-ratio to the image or wrapper |
| Horizontal scrolling on phones | Grid minimum or child content is wider than the viewport | Use min(100%, ...), min-width: 0, and inspect captions |
| Important subject is cropped | cover centers the crop poorly |
Change object-position, use contain, or provide an art-directed crop |
| Layout jumps while loading | Missing intrinsic dimensions | Add accurate width and height attributes |
| Browser downloads an unexpectedly large file | Incorrect sizes or missing candidates |
Describe the actual rendered width and provide suitable srcset files |
| Lazy images appear late | The image is near or above the initial viewport | Remove loading="lazy" for critical images |
| Alt text is read twice | Caption and alt contain the same sentence | Keep alt concise and let the caption carry the additional context |
9. Performance, reliability, and cost considerations
- Generate a small, medium, and large version of each image rather than sending a full-resolution original to every phone.
- Use modern formats where your image pipeline supports them, while keeping a working fallback.
- Reserve layout space with dimensions and avoid expensive client-side resizing after load.
- Test at the narrowest supported width, a tablet width, a wide desktop, and with slow network conditions.
- Do not assume one breakpoint or one aspect ratio fits every content type; portraits, screenshots, and diagrams need different treatment.
The gallery itself has no service cost when it uses files you already host. Your actual bandwidth, storage, image-processing, and CDN costs depend on your hosting setup; the source material does not provide a universal benchmark.
10. Or skip the browser setup
If you need rendered screenshots of gallery pages for documentation, previews, tests, or social cards, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. See the ScreenshotNeo API docs for all options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie and consent banners are accepted and removed before the shot, along with more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing state. ScreenshotNeo also provides an MCP server for AI agents with take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
11. FAQ
Should I use Grid or Flexbox?
Use Grid when you need rows and columns. Flexbox is useful for a single row or column, but Grid gives a gallery consistent two-dimensional tracks.
Does srcset make a gallery layout responsive?
No. CSS controls placement and frame size; srcset and sizes control the selected file.
How do I stop important details being cut off?
Use contain, adjust object-position, change the frame ratio, or provide a separate crop with picture.
Do intrinsic dimensions have to match the CSS ratio?
They should describe the source file accurately. CSS can intentionally display another ratio, but declare that display ratio with aspect-ratio so layout space remains predictable.


