How to build a website thumbnail gallery from URLs with HTML and CSS
Build a responsive thumbnail gallery from image URLs with semantic HTML, CSS Grid, consistent crops, accessible alt text, and responsive image sources.
A responsive thumbnail gallery needs just a semantic list of <img> elements and a CSS Grid. Use object-fit: cover to keep thumbnail boxes consistent, meaningful alt text for informative images, and real image dimensions to reserve space while each URL loads.
1. Build the basic gallery
This example works with local paths or absolute image URLs. Replace the sample paths, descriptions, and intrinsic dimensions with your own. The width and height attributes should describe each source image’s actual dimensions; CSS controls the displayed thumbnail size.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Image gallery</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
padding: 2rem;
font-family: system-ui, sans-serif;
color: #172033;
background: #f5f7fb;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
gap: 1rem;
max-width: 72rem;
margin: 0 auto;
padding: 0;
list-style: none;
}
.gallery img {
display: block;
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 0.5rem;
background: #e3e8f0;
}
</style>
</head>
<body>
<main>
<h1>Places to explore</h1>
<ul class="gallery">
<li><img src="https://images.example.com/coast.jpg" alt="Rocky coast beside blue water" width="1200" height="900" loading="lazy"></li>
<li><img src="https://images.example.com/forest.jpg" alt="Sunlight through a forest canopy" width="1200" height="900" loading="lazy"></li>
<li><img src="https://images.example.com/city.jpg" alt="Buildings along a city street" width="1200" height="900" loading="lazy"></li>
</ul>
</main>
</body>
</html>
The list gives the related images collection semantics. repeat(auto-fit, minmax(...)) fits as many columns as the available width allows, then lets those columns share remaining space. Adjust 12rem to set the preferred minimum tile width and gap to set spacing. The min(100%, 12rem) guard lets a single column fit very narrow screens without overflowing.
An img needs a usable src and an appropriate alt. Describe the relevant content when the image conveys information. Use alt="" when the image is decorative or its meaning is already fully provided by nearby text, so assistive technology does not repeat it. See MDN’s img reference.
2. Choose how each thumbnail fits its frame
The aspect-ratio sets the frame shape independently of the source image. With object-fit: cover, the image fills the frame and the excess is cropped. This is usually suitable for a uniform gallery, but it can cut off parts of a subject.
| Setting | Result | Use it when |
|---|---|---|
object-fit: cover |
Fills the frame; may crop the image. | Uniform tiles matter more than showing every edge. |
object-fit: contain |
Shows the complete image; may leave empty bands. | The whole image must remain visible, such as a diagram or product. |
Set the crop focus with object-position. Its default centers the image; values such as center top or 65% 35% shift the focal point.
.gallery img.portrait {
object-position: center 25%;
}
.gallery img.uncropped {
object-fit: contain;
background: #e3e8f0;
}
Apply a class to the relevant image when particular subjects need a different crop. If you need completely different art direction, such as a landscape crop on desktop and a portrait crop on mobile, use <picture> with alternate sources and retain an <img> fallback. MDN explains picture and source selection.
3. Load appropriate image files for different screens
If you have only one file per image, a plain src is enough. If you provide multiple resolutions, srcset and sizes let the browser select a suitable candidate for the rendered slot. Width-descriptor candidates (such as 640w) need a sizes attribute describing the expected rendered width.
<img
src="https://images.example.com/coast-800.jpg"
srcset="https://images.example.com/coast-400.jpg 400w,
https://images.example.com/coast-800.jpg 800w,
https://images.example.com/coast-1200.jpg 1200w"
sizes="(min-width: 72rem) 23rem, (min-width: 40rem) 30vw, 100vw"
alt="Rocky coast beside blue water"
width="1200"
height="900"
loading="lazy">
Make sizes reflect your actual grid and page width; the example is only a starting point. A browser can use the candidate widths, the expected slot size, and its device pixel ratio to choose a resource. Responsive sources can avoid downloading an unnecessarily large original on a small display. For an overview, see MDN’s guide to responsive images and the WHATWG image specification.
Use <picture> when the image itself should change by media condition or format, for example a different crop at a narrow breakpoint or an AVIF file with a fallback. Keep a regular <img> inside it: that element supplies the fallback and the alternative text.
<picture>
<source
media="(max-width: 40rem)"
srcset="https://images.example.com/coast-portrait.webp"
type="image/webp">
<source
srcset="https://images.example.com/coast-landscape.avif"
type="image/avif">
<img
src="https://images.example.com/coast-landscape.jpg"
alt="Rocky coast beside blue water"
width="1200"
height="900"
loading="lazy"
class="gallery-image">
</picture>
When using <picture>, style its contained image to fill the gallery tile. Ensure each alternate source has the intended dimensions and crop. More details are in the WHATWG embedded content standard.
4. Reserve layout space and load below-fold images lazily
Set width and height attributes to each source image’s intrinsic dimensions, or otherwise reserve its correct ratio with CSS. The browser can use that geometry before the download completes, reducing layout shifts. This matters for lazy-loaded images too; lazy loading does not reserve space on its own. The WHATWG standard encourages dimensions for lazy images to prevent layout movement. See the HTML embedded content guidance.
Use loading="lazy" for images well below the initial viewport. Avoid lazy-loading the image that is immediately visible and central to the page; let it load normally. Lazy loading is a browser hint, not a guarantee of a particular load schedule.
5. Make remote image URLs dependable
- Use a direct image URL that returns an image, not a web page that displays an image in a viewer.
- Prefer HTTPS URLs on a trusted image host; check that the host permits embedding from your site.
- For signed or expiring URLs, ensure their validity extends through page viewing and that refresh logic can obtain a new URL.
- Keep the dimensions and aspect ratio accurate even for remote files so the layout is stable before they load.
- Use descriptive alternative text, and consider a neutral background so an unavailable image does not leave a visually confusing blank.
- If images are user supplied, validate their URLs and avoid inserting untrusted strings into HTML. Set the DOM
srcproperty or escape values in a server-rendered template.
For a gallery generated by JavaScript, create elements and set attributes instead of concatenating user-controlled URL strings into innerHTML:
const gallery = document.querySelector(".gallery");
const images = [
{ url: "https://images.example.com/coast.jpg", alt: "Rocky coast beside blue water" },
{ url: "https://images.example.com/forest.jpg", alt: "Sunlight through a forest canopy" }
];
for (const item of images) {
const li = document.createElement("li");
const img = document.createElement("img");
img.src = item.url;
img.alt = item.alt;
img.width = 1200;
img.height = 900;
img.loading = "lazy";
li.append(img);
gallery.append(li);
}
6. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Broken image icon | The URL is incorrect, expired, blocked, or returns a non-image response. | Open the exact URL directly, inspect the response status and content type in browser developer tools, and check host embedding rules. |
| Tiles have uneven heights | The image or frame has no shared ratio, or a rule is being overridden. | Set a consistent aspect-ratio and object-fit; inspect computed styles and conflicting CSS. |
| Subjects are cut off | cover crops image edges around the chosen focal point. |
Change object-position, use a different crop, or switch that image to contain. |
| Gallery overflows on a narrow screen | The grid’s minimum column width or page padding exceeds the viewport. | Use minmax(min(100%, 12rem), 1fr) or lower the minimum and check parent width and padding. |
| Page jumps when images appear | Image geometry was not reserved or dimensions are inaccurate. | Add the actual intrinsic width and height, and keep the CSS aspect ratio aligned with the intended frame. |
| Wrong resolution appears | sizes does not describe the actual slot, or candidates are missing/badly labeled. |
Match each w descriptor to the file width and adjust sizes to your grid. Check the selected source in developer tools. |
| Image differs from expected after navigation | A URL may redirect, expire, or return different content based on authorization or request headers. | Use a stable public asset URL or refresh the signed URL; verify redirects and access requirements. |
7. Performance, reliability, and cost
For a small static gallery, HTML and CSS need no library or server-side image processing. The main cost is fetching and decoding the image files. Use appropriately sized candidates, compress and convert assets where your image pipeline allows, and lazy-load below-fold tiles. There is no universal speed improvement percentage: actual results depend on source file sizes, connection, browser, and layout.
Remote URLs add dependencies on the image host, its availability, cache behavior, and embedding policy. For an important gallery, use a stable host you control or a reliable image delivery setup, and have a plan to replace expired or removed assets. Test representative mobile and desktop widths, plus the unavailable-image state.
If you need a rendered screenshot of the finished page for a report, preview, or automation workflow, ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. Its API can return PNG, JPEG, WebP, or PDF, and its MCP tools let compatible AI agents take screenshots, inspect page information, and capture PDFs. See the ScreenshotNeo documentation.
Or skip the browser setup
For a one-call capture of a page containing your gallery, use ScreenshotNeo’s API. Create an API key and replace the example page URL with your deployed gallery URL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/gallery -o gallery.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/gallery"},
timeout=90,
)
r.raise_for_status()
open("gallery.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/gallery'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('gallery.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, and failed loads are not billed, and cache hits cost nothing. Responses include X-Page-Verdict and X-Billed headers. An MCP server lets AI agents use the capture tools. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Sign up for free.
FAQ
Can a thumbnail gallery use image URLs from another domain?
Yes, if the URL returns an embeddable image and that host permits your page to display it. A web-page URL that merely shows an image is not itself an image source.
Do I need JavaScript to make the gallery responsive?
No. CSS Grid handles the responsive column layout. JavaScript is useful only when the image list is generated or updated dynamically.
Should every thumbnail have alt text?
Every image should have an appropriate alt value. Describe informative content; use an empty value for decorative images or when adjacent text already conveys the same information.
When should I use a link around each image?
Make a thumbnail a link when it navigates to a larger image, detail page, or related content. Give the link a clear accessible name, often through the image’s alternative text or accompanying visible text.


