CSS Tutorial
CSS Project: Photo Gallery
A responsive image grid with captions. Gap, object-fit, and a hover lift keep the pictures in line.
What you will build
A six-photo wall for Harbor studio. Each cell is a figure: a cropped image and a caption. The grid holds a steady gap. On a wide window you get three columns. On a narrow one, two, then one. Hover lifts a frame without knocking the others out of line.
Use real images. The examples point at picsum.photos with stable seeds so the preview is not an empty box. Alt text still names what is in the shot.
Open an example with Try it in CSS. That loads /css/try — a live page preview.
Properties you will use
| Property | Job on this page |
|---|---|
display: grid | Builds the photo wall |
gap | Keeps even space between frames |
object-fit: cover | Fills a fixed-height crop without stretching |
grid-template-columns | Sets two or three columns with auto-fit |
transform on :hover | Lifts one figure without reflow |
Figures, not bare images. A caption is part of the cell. If you float images with margin hacks, the gap will fight you.
Build in slices
First lock the crop. One figure, one image, one caption. Height is fixed. object-fit: coverdoes the cropping.
Example
<style>
body { font-family: Georgia, serif; background: #e0f2fe; color: #0c4a6e; margin: 1.5rem; }
figure {
margin: 0;
background: #fff;
border-radius: 0.85rem;
overflow: hidden;
box-shadow: 0 10px 22px rgba(12, 74, 110, 0.12);
}
img {
display: block;
width: 100%;
height: 11rem;
object-fit: cover;
}
figcaption { padding: 0.7rem 0.85rem 0.85rem; font-size: 0.95rem; }
</style>
<figure>
<img src="https://picsum.photos/seed/harbor-window/640/400" alt="Morning light on the studio window">
<figcaption>Window table, 8:10</figcaption>
</figure>Then wrap several figures in a grid. auto-fit and a minimum column width do the responsive work without a media query yet.
Example
<style>
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
gap: 1rem;
}
figure { transition: transform 0.18s ease, box-shadow 0.18s ease; }
figure:hover {
transform: translateY(-6px);
box-shadow: 0 16px 28px rgba(12, 74, 110, 0.18);
}
</style>
<div class="gallery">
<figure>
<img src="https://picsum.photos/seed/harbor-loaf/640/400" alt="Sourdough cooling on a wooden board">
<figcaption>Sourdough, still warm</figcaption>
</figure>
<figure>
<img src="https://picsum.photos/seed/harbor-cup/640/400" alt="Espresso in a small cup">
<figcaption>First espresso</figcaption>
</figure>
</div>Resize the preview at /css/try. Columns should drop before captions wrap into a cramped strip.
Complete page
Six frames from a Harbor studio morning. The page heading sits above the grid. Hover lift is transform only, so neighbors do not jump.
Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Harbor studio — morning wall</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
background: #e0f2fe;
color: #0c4a6e;
font-family: Georgia, "Times New Roman", serif;
}
header {
padding: 1.6rem 1.25rem 0.4rem;
max-width: 68rem;
margin: 0 auto;
}
h1 { margin: 0 0 0.35rem; font-size: clamp(1.6rem, 3vw, 2.1rem); }
.lead { margin: 0 0 1.2rem; color: #075985; }
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
gap: 1rem;
max-width: 68rem;
margin: 0 auto;
padding: 0 1.25rem 2rem;
}
figure {
margin: 0;
background: #fff;
border-radius: 0.85rem;
overflow: hidden;
box-shadow: 0 10px 22px rgba(12, 74, 110, 0.12);
transition: transform 0.18s ease, box-shadow 0.18s ease;
}
figure:hover {
transform: translateY(-6px);
box-shadow: 0 16px 28px rgba(12, 74, 110, 0.18);
}
img {
display: block;
width: 100%;
height: 11rem;
object-fit: cover;
}
figcaption {
padding: 0.7rem 0.85rem 0.9rem;
font-size: 0.95rem;
}
</style>
</head>
<body>
<header>
<h1>Harbor studio — morning wall</h1>
<p class="lead">Six frames from the window side, shot before the cafe next door opened.</p>
</header>
<div class="gallery">
<figure>
<img src="https://picsum.photos/seed/harbor-window/640/400" width="640" height="400" alt="Morning light on the studio window">
<figcaption>Window table, 8:10</figcaption>
</figure>
<figure>
<img src="https://picsum.photos/seed/harbor-loaf/640/400" width="640" height="400" alt="Sourdough cooling on a wooden board">
<figcaption>Sourdough, still warm</figcaption>
</figure>
<figure>
<img src="https://picsum.photos/seed/harbor-cup/640/400" width="640" height="400" alt="Espresso in a small cup beside a saucer">
<figcaption>First espresso</figcaption>
</figure>
<figure>
<img src="https://picsum.photos/seed/harbor-press/640/400" width="640" height="400" alt="Prints hanging to dry on a studio line">
<figcaption>Prints on the line</figcaption>
</figure>
<figure>
<img src="https://picsum.photos/seed/harbor-pier/640/400" width="640" height="400" alt="Fishing boats along the pier">
<figcaption>Pier from the door</figcaption>
</figure>
<figure>
<img src="https://picsum.photos/seed/harbor-menu/640/400" width="640" height="400" alt="A handwritten cafe menu on cream paper">
<figcaption>Today's board</figcaption>
</figure>
</div>
</body>
</html>Practice tasks
- Raise the image height to
14remand the gap to1.25rem. Check that captions still sit under the crop. - Change
minmax(14rem, 1fr)tominmax(18rem, 1fr)so a tablet shows two columns sooner. - Add a seventh figure with a new picsum seed. Preview the wrap at /css/try.