How to build an HTML image gallery

A gallery is a CSS grid of images with captions and a lightbox. The hard part is not the layout, it is that the pictures stop loading the moment the file leaves your folder.

An HTML image gallery is a CSS grid of <figure> elements, and one line of grid CSS handles every screen width without a media query.

The layout takes ten minutes. What costs an afternoon is images that show on your machine and nowhere else, so that part gets its own section below.

A gallery page with tiles in an even grid, each with a caption underneath.
A gallery page with tiles in an even grid, each with a caption underneath.

The grid

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}

auto-fill means the browser fits as many 220px columns as the width allows and shares the leftover space. Four columns on a laptop, one on a phone, and nothing to configure.

Raise the minimum for fewer, larger tiles. Lower it for a contact sheet. That single number is the whole density control.

Even rows and sensible cropping

Mixed portrait and landscape photos make ragged rows. Fix the tile shape and let the image fill it.

.gallery img {
  width: 100%; aspect-ratio: 4 / 3;
  object-fit: cover; display: block;
  border-radius: 6px;
}

object-fit: cover crops from the centre. For product shots where the edges matter, use contain and accept letterboxing instead.

Setting aspect-ratio also reserves the space before the image loads, so the page does not jump as pictures arrive.

Captions and alt text are different things

A caption is for everyone. Alt text is for people who cannot see the image, and for when the image fails to load.

<figure>
  <img src="..." alt="Assembly line at the Busan plant, 2026" loading="lazy">
  <figcaption>Busan plant, March 2026</figcaption>
</figure>

Write alt text that describes what is in the frame, not the file name. Alt text covers the cases where the correct value is an empty string.

Add loading="lazy" to everything below the first row. Leave it off the first few so the top of the page is not blank on arrival.

Making the images actually travel

This is where galleries break. Three options, and only two of them survive being shared.

Approach Page stays one file Works offline Page weight Good for
Relative folder paths No Only with the folder Small file Nothing you send
Base64 data URIs Yes Yes Large file Under about 15 images
Full https image URLs Yes No Small file Large galleries

Relative paths are the default and the reason images stop showing in HTML. The reference points at a folder that only exists on your machine.

Base64 data URIs fold the pictures into the markup. The page becomes self-contained, at the cost of size, so resize before encoding.

The same gallery opened from a different folder. Every tile shows a broken image icon.
The same gallery opened from a different folder. Every tile shows a broken image icon.

A lightbox without JavaScript

Click to enlarge can be done with anchors and the :target pseudo-class.

Each thumbnail links to #photo-3. A hidden full-size container with that id becomes visible when it is the target, and a close link points back at #gallery.

That covers open and close. Arrow-key navigation and swipe need script, so add them only if the gallery is long enough to justify it.

Whatever you use, keep the enlarged image inside the viewport with max-height: 90vh and object-fit: contain.

Weight, and why it matters more than it used to

A gallery is the heaviest kind of page most people build. Three habits keep it usable.

  • Resize before embedding. A 4000px photo displayed at 300px wastes almost all of its bytes.
  • Give the browser widths. srcset with two or three sizes lets phones fetch the small one.
  • Cap the count per page. Past about forty tiles, split into sections or pages.

If the gallery is a portfolio rather than an archive, cut harder. Turning a portfolio into a link covers the selection question.

Order, grouping, and the first row

The first row of a gallery is most of the impression. Put the strongest images there and do not rely on the reader scrolling.

Group by section when the gallery covers more than one subject. A heading every eight to twelve tiles gives the eye somewhere to rest and makes the page skimmable.

Chronological order is the default and often the wrong one. Sort by what the reader came for, and note the date in the caption instead.

If two images are near duplicates, keep one. A gallery is an edit, and every extra tile makes the good ones slightly harder to notice.

A gallery sent as a file is the single worst case for attachments. The markup arrives, the pictures do not, and the reader sees broken icons.

  1. Open the file in the HTML file opener. If the images survive in a window that has never seen your folder, they will survive for readers.
  2. Paste the HTML into a NOS document. The grid and the lightbox render as written.
  3. Share, then Share link, then Create link. Tick Public on the web only if the gallery should turn up in search results.
  4. Send the link.
The share dialog with the link created, Public on the web available as a checkbox.
The share dialog with the link created, Public on the web available as a checkbox.

Adding a photo later means editing the page, not sending a second file. The address does not change, so the link already points at the longer gallery.

The gallery in a narrow window. The grid has collapsed to a single column with captions intact.
The gallery in a narrow window. The grid has collapsed to a single column with captions intact.

Before you send it

  • Does every image load in a window that has never seen your project folder?
  • Does every <img> have an alt value, or a deliberate empty one?
  • Is the total page weight reasonable, after resizing?
  • Does the grid collapse to one column on a phone rather than scrolling sideways?
  • Is there a <title>, and should the link stay unlisted?

Questions people ask

What is the simplest layout for an HTML image gallery?

A CSS grid with repeat(auto-fill, minmax(220px, 1fr)). The number of columns adapts to the window with no media queries at all, and every tile keeps a sensible minimum width on a phone.

Why do the images disappear when I send the HTML file?

The file references images by folder path, and the folder did not travel with it. Either embed the images as data URIs so the page is self-contained, or host the images at full addresses and reference those.

Do I need JavaScript for a lightbox?

Not necessarily. A CSS-only lightbox using the target pseudo-class covers click to enlarge and close. You need script only for keyboard arrow navigation between images and for swipe gestures.

How do I share an image gallery without hosting a site?

Paste the gallery HTML into a NOS document and create a share link. The page is served at its own address, so the grid, the captions and the lightbox work for the reader in one click, on desktop or phone.

Keep reading