Image Gallery
Photo galleries appear everywhere: hotel rooms, product photos, event pictures, a designer's portfolio. A good gallery fits any screen, keeps its thumbnails tidy even when the pictures have different shapes, and lets visitors open a bigger view with the mouse, a finger or the keyboard.
In this lesson you will build one step by step. It brings together CSS Grid, aspect-ratio and object-fit from Sizing, Aspect Ratio & Overflow, and the <dialog> element from Details, Summary & Dialog.
The HTML: a list of figures
A gallery is a set of similar items, so a list is a good container. Each item is a <figure>: the image plus its visible caption in <figcaption>.
<ul class="gallery">
<li>
<figure>
<img src="https://course.ethicscomputer.in/assets/samples/portrait.svg" width="400" height="600"
alt="A tall brick clock tower with a round white clock face, under a blue sky">
<figcaption>The old clock tower, poster colours by Kabir</figcaption>
</figure>
</li>
<!-- more items -->
</ul>A few rules make this markup work well:
- The alt text describes what the picture shows, for people who cannot see it. The caption adds context, such as the title and the artist. Screen readers read both, so do not repeat one in the other.
widthandheightare the image's real size. The browser uses them to reserve space before the image loads, so the page does not jump.- Add
loading="lazy"when the gallery sits further down a long page, so images load only as the visitor scrolls near them. Leave it off images that are visible as soon as the page opens. Both points are covered in HTML Images.
A responsive grid with tidy thumbnails
The grid is the one-line pattern from CSS Grid Basics: as many columns as fit, each at least 10rem wide. min(100%, 10rem) stops a column from being wider than the screen on a very small phone.
Real pictures rarely share one shape. This gallery has a wide landscape, a tall tower, a square avatar, a long logo and a small icon. Left alone, they would make a ragged grid. Two properties fix that:
aspect-ratio: 4 / 3gives every image box the same shape.object-fit: coverfills that box and crops whatever does not fit, without stretching the picture.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gallery grid</title>
<style>
body { margin: 0; padding: 1rem; font-family: system-ui, sans-serif; }
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
gap: 1rem;
margin: 0;
padding: 0;
list-style: none;
}
.gallery figure { margin: 0; }
.gallery img {
box-sizing: border-box;
display: block;
width: 100%;
height: auto;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 8px;
background: #f1f5f9;
}
.gallery .crop-top { object-position: top; }
.gallery .show-whole { object-fit: contain; padding: 0.75rem; }
.gallery figcaption { margin-top: 0.4rem; font-size: 0.875rem; color: #475569; }
</style>
</head>
<body>
<h1>Kochi College Art Club: 2026 show</h1>
<ul class="gallery">
<li><figure>
<img src="https://course.ethicscomputer.in/assets/samples/landscape-400.svg" width="400" height="250"
alt="Blue and green hills beside a winding river, with an orange sun rising">
<figcaption>Sunrise in the hills, watercolour by Priya</figcaption>
</figure></li>
<li><figure>
<img class="crop-top" src="https://course.ethicscomputer.in/assets/samples/portrait.svg" width="400" height="600"
alt="A tall brick clock tower with a round white clock face, under a blue sky">
<figcaption>The old clock tower, poster colours by Kabir</figcaption>
</figure></li>
<li><figure>
<img src="https://course.ethicscomputer.in/assets/samples/avatar.svg" width="200" height="200"
alt="Cartoon of a smiling person with dark hair and a green top">
<figcaption>Self-portrait, digital art by Rohan</figcaption>
</figure></li>
<li><figure>
<img class="show-whole" src="https://course.ethicscomputer.in/assets/samples/logo.svg" width="240" height="80"
alt="Chai Corner logo: a brown tea cup with steam beside the shop name">
<figcaption>Logo for a tea stall, by Meera</figcaption>
</figure></li>
<li><figure>
<img class="show-whole" src="https://course.ethicscomputer.in/assets/samples/icon.svg" width="64" height="64"
alt="A yellow star on a purple rounded square">
<figcaption>App icon, by Farhan</figcaption>
</figure></li>
</ul>
</body>
</html>Drag the editor narrower and wider: the number of columns changes, but every thumbnail keeps the same shape. Look closely at three choices:
- The clock tower uses
object-position: top. With plaincover, the middle of the picture is kept and the top of the tower is cut off;topkeeps the roof and the clock instead. - The logo and icon use
object-fit: containwith some padding. Cropping is fine for scenery, but a cropped logo loses its name.containshows the whole image and lets the grey background fill the spare space. display: blockremoves the small gap that appears under images, because images are inline by default and sit on the text baseline like letters.
Try removing aspect-ratio: 4 / 3 to see the ragged version, then put it back.
Hover and focus effects
Visitors should be able to open each picture. Wrap each image in a link to the full-size file, and give the link a gentle zoom on hover. Anything that reacts to the mouse must react to the keyboard too, so the same rule uses :focus-visible:
.thumb {
display: block;
overflow: hidden; /* keeps the zoomed image inside its box */
border-radius: 8px;
}
.thumb img {
transition: scale 250ms ease-out;
}
.thumb:hover img,
.thumb:focus-visible img {
scale: 1.06;
}
.thumb:focus-visible {
outline: 3px solid #f59e0b;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.thumb img { transition: none; }
}The overflow: hidden on the link clips the slightly enlarged image, so the zoom happens inside the thumbnail. The outline is not clipped, because an element's own outline is drawn outside its box. Keep the captions always visible rather than showing them only on hover: touch screen users have no hover, so they would never see them.
A lightbox with dialog
A lightbox shows a large version of the picture over a dimmed page. The <dialog> element is made for this. Opened with showModal(), it puts a backdrop behind itself, moves focus inside, closes with Esc and returns focus to the thumbnail afterwards. A few lines of script connect the thumbnails to it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gallery with lightbox</title>
<style>
body { margin: 0; padding: 1rem; font-family: system-ui, sans-serif; }
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
gap: 1rem;
margin: 0;
padding: 0;
list-style: none;
}
.gallery figure { margin: 0; }
.gallery figcaption { margin-top: 0.4rem; font-size: 0.875rem; color: #475569; }
.thumb { display: block; overflow: hidden; border-radius: 8px; }
.thumb img {
box-sizing: border-box;
display: block;
width: 100%;
height: auto;
aspect-ratio: 4 / 3;
object-fit: cover;
background: #f1f5f9;
transition: scale 250ms ease-out;
}
.thumb:hover img, .thumb:focus-visible img { scale: 1.06; }
.thumb:focus-visible { outline: 3px solid #f59e0b; outline-offset: 3px; }
.crop-top { object-position: top; }
.gallery .show-whole { object-fit: contain; padding: 0.75rem; }
@media (prefers-reduced-motion: reduce) { .thumb img { transition: none; } }
.lightbox {
width: min(90vw, 44rem);
padding: 1rem;
border: 0;
border-radius: 10px;
}
.lightbox::backdrop { background: rgb(0 0 0 / 0.8); }
.lightbox figure { margin: 0; }
.lightbox img {
display: block;
width: 100%;
height: auto;
max-height: 70dvh;
object-fit: contain;
}
.lightbox figcaption { margin-top: 0.5rem; text-align: center; }
.lightbox form { text-align: center; }
.lightbox button { margin-top: 0.75rem; padding: 0.5rem 1.25rem; font: inherit; }
</style>
</head>
<body>
<h1>Kochi College Art Club: 2026 show</h1>
<ul class="gallery">
<li><figure>
<a class="thumb" href="https://course.ethicscomputer.in/assets/samples/landscape.svg"><img src="https://course.ethicscomputer.in/assets/samples/landscape-400.svg" width="400" height="250"
alt="Blue and green hills beside a winding river, with an orange sun rising"></a>
<figcaption>Sunrise in the hills, watercolour by Priya</figcaption>
</figure></li>
<li><figure>
<a class="thumb" href="https://course.ethicscomputer.in/assets/samples/portrait.svg"><img class="crop-top" src="https://course.ethicscomputer.in/assets/samples/portrait.svg" width="400" height="600"
alt="A tall brick clock tower with a round white clock face, under a blue sky"></a>
<figcaption>The old clock tower, poster colours by Kabir</figcaption>
</figure></li>
<li><figure>
<a class="thumb" href="https://course.ethicscomputer.in/assets/samples/avatar.svg"><img src="https://course.ethicscomputer.in/assets/samples/avatar.svg" width="200" height="200"
alt="Cartoon of a smiling person with dark hair and a green top"></a>
<figcaption>Self-portrait, digital art by Rohan</figcaption>
</figure></li>
<li><figure>
<a class="thumb" href="https://course.ethicscomputer.in/assets/samples/logo.svg"><img class="show-whole" src="https://course.ethicscomputer.in/assets/samples/logo.svg" width="240" height="80"
alt="Chai Corner logo: a brown tea cup with steam beside the shop name"></a>
<figcaption>Logo for a tea stall, by Meera</figcaption>
</figure></li>
</ul>
<dialog class="lightbox" aria-labelledby="lightbox-caption">
<figure>
<img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" alt="Blue and green hills beside a winding river, with an orange sun rising">
<figcaption id="lightbox-caption">Sunrise in the hills, watercolour by Priya</figcaption>
</figure>
<form method="dialog">
<button>Close</button>
</form>
</dialog>
<script>
const lightbox = document.querySelector(".lightbox");
const bigImage = lightbox.querySelector("img");
const bigCaption = lightbox.querySelector("figcaption");
document.querySelector(".gallery").addEventListener("click", (event) => {
const link = event.target.closest(".thumb");
if (!link) return;
event.preventDefault(); // stay on this page instead of opening the file
bigImage.src = link.href;
bigImage.alt = link.querySelector("img").alt;
bigCaption.textContent = link.closest("figure").querySelector("figcaption").textContent;
lightbox.showModal();
});
</script>
</body>
</html>Click a thumbnail, or Tab to one and press Enter. The large picture opens over a dark backdrop. Close it with the button or Esc, and focus lands back on the thumbnail you opened, so a keyboard user does not lose their place.
Some details worth noticing:
- The thumbnails are real links to the full-size files. If the script fails to load, a click still opens the picture. The script only takes over with
preventDefault(). - The first thumbnail uses the small
landscape-400.svgfile, while its link points to the largelandscape.svg. Real galleries do the same: small files for the grid, big files only when asked for. Responsive Images shows howsrcsettakes this further. - In the lightbox,
width: 100%withobject-fit: containandmax-height: 70dvhfits any shape of picture inside the screen without stretching it.dvhis the dynamic viewport height, which stays correct on phones when the browser's address bar slides in and out. aria-labelledbynames the dialog after the caption, so a screen reader announces which picture opened.
Common mistakes
- Images without
widthandheight. The page jumps as each picture loads. object-fit: coveron logos, charts or screenshots. Important parts get cropped. Usecontainfor them.- Captions only on hover. Touch screen users never see them. Keep captions visible.
- Alt text like "image1.jpg" or "photo". Describe what the picture shows.
- A lightbox built from a plain
<div>. You then have to handle focus, Esc and the backdrop yourself. A modal<dialog>does it for you.
Key points
- Mark up a gallery as a list of
<figure>elements, each with an<img>and a<figcaption>. repeat(auto-fit, minmax(min(100%, 10rem), 1fr))gives a responsive grid without media queries.aspect-ratiomakes every thumbnail the same shape;object-fit: covercrops photos andcontainkeeps logos whole.object-positionchooses which part of a cropped picture stays visible.- Pair every
:hovereffect with:focus-visible, and keep captions visible. - A modal
<dialog>makes an accessible lightbox; link thumbnails to the full files so they work without script.
Exercise
Make a gallery for Isha's sketchbook with four items: the landscape, the clock tower, the avatar and the star icon from the sample images. Use square thumbnails (aspect-ratio: 1) in a grid whose columns are at least 8rem wide. Crop the landscape, tower and avatar with cover, but show the icon whole with contain. Give each picture a caption and correct alt text, and add a coloured outline when a thumbnail link is hovered or focused.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Isha's sketchbook</title>
<style>
body { font-family: system-ui, sans-serif; padding: 1rem; }
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 8rem), 1fr));
gap: 0.75rem;
padding: 0;
list-style: none;
}
figure { margin: 0; }
.gallery a { display: block; border-radius: 6px; }
.gallery a:hover,
.gallery a:focus-visible { outline: 3px solid #7c3aed; outline-offset: 2px; }
.gallery img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 1;
object-fit: cover;
border-radius: 6px;
background: #f3f4f6;
}
.gallery .whole { object-fit: contain; }
figcaption { font-size: 0.875rem; margin-top: 0.3rem; }
</style>
</head>
<body>
<ul class="gallery">
<li><figure>
<a href="https://course.ethicscomputer.in/assets/samples/landscape.svg"><img src="https://course.ethicscomputer.in/assets/samples/landscape-400.svg" width="400" height="250" alt="Hills and a river at sunrise"></a>
<figcaption>Morning view</figcaption>
</figure></li>
<li><figure>
<a href="https://course.ethicscomputer.in/assets/samples/portrait.svg"><img src="https://course.ethicscomputer.in/assets/samples/portrait.svg" width="400" height="600" alt="A tall brick clock tower under a blue sky"></a>
<figcaption>Clock tower</figcaption>
</figure></li>
<li><figure>
<a href="https://course.ethicscomputer.in/assets/samples/avatar.svg"><img src="https://course.ethicscomputer.in/assets/samples/avatar.svg" width="200" height="200" alt="Cartoon of a smiling person"></a>
<figcaption>A friend</figcaption>
</figure></li>
<li><figure>
<a href="https://course.ethicscomputer.in/assets/samples/icon.svg"><img class="whole" src="https://course.ethicscomputer.in/assets/samples/icon.svg" width="64" height="64" alt="A yellow star on a purple square"></a>
<figcaption>Star badge</figcaption>
</figure></li>
</ul>
</body>
</html>aspect-ratio: 1 makes every thumbnail square, cover crops the three pictures neatly, and the whole class switches the icon to contain. The outline appears for both the mouse and the keyboard.
Quick Quiz
1. What does grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr)) do?
auto-fit with minmax() adds columns as space allows, and min(100%, 10rem) prevents overflow on tiny screens.
2. A logo is shown in a 4:3 thumbnail box. Which object-fit value keeps the whole logo visible without distorting it?
contain scales the image to fit inside the box completely; cover would crop it and fill would stretch it.
3. Why should gallery thumbnails be links to the full-size images?
The links work on their own; the script only improves them by opening a dialog instead.
4. What does a <dialog> opened with showModal() give a lightbox for free?
A modal dialog handles the backdrop, Esc and focus, which a plain <div> would need extra code for.
Learn CSS with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
