In short
A gallery with thumbnails, a full-screen view and arrow-key controls looks great in a portfolio. It also makes you think about loading images smartly and about keyboard users.
A responsive image grid with a lightbox/modal viewer — practice for CSS Grid, modal UI patterns, and keyboard navigation (arrows, Esc to close). The project combines responsive layout, stateful modal behavior, image loading, keyboard accessibility, focus management, and efficient media handling.

Think of it like this
Your phone's gallery app
A grid of small thumbnails. Tap one and it opens full screen, swipe for the next, tap outside to close. You're building that same thing for the web, with clicks and arrow keys in place of swipes.
Watch it happen
Next and previous with wrap-around
1/5Click the first thumbnail. show(0) opens the dialog with goa.
The big ideas
What you need to know
Responsive grid
CSS Grid with repeat(auto-fill, minmax(150px, 1fr)) gives a grid that fits any screen.
Lazy-load the images
Add loading="lazy" so images below the fold load only when the user scrolls near them.
Use <dialog> for the lightbox
dialog.showModal() opens a proper modal. Escape closes it for free and focus stays inside.
Keyboard and alt text
Arrow keys for next and previous, and a meaningful alt on every image for screen readers.
How it works
Build plan
- 1
Image data
An array of objects with thumbnail URL, full-size URL and alt text.
- 2
Render the grid
Make thumbnails from the array with lazy loading.
- 3
Open the lightbox
Clicking a thumbnail opens a <dialog> with the full-size image.
- 4
Next and previous
Buttons and arrow keys that wrap around at both ends.
- 5
Polish
Captions, an image counter like 3 / 12, and a loading state for big images.
See it in code
Walk through the code
1const images = ["goa.jpg", "manali.jpg", "hampi.jpg"];2let current = 0;3const dialog = document.querySelector("dialog");4const big = dialog.querySelector("img");5 6function show(index) {7 current = (index + images.length) % images.length; // wraps at both ends8 big.src = images[current];9 if (!dialog.open) dialog.showModal();10}11 12document.addEventListener("keydown", (e) => {13 if (!dialog.open) return;14 if (e.key === "ArrowRight") show(current + 1);15 if (e.key === "ArrowLeft") show(current - 1);16});1/4The image list, the current index and the dialog parts.
Adding images.length before % keeps the index positive, so pressing Left on the first image jumps to the last one instead of breaking.
Save yourself the pain
Common mistakes
Oops
Using full-size 4 MB photos as thumbnails.
Do this instead
Make small thumbnail versions and load the big one only in the lightbox.
Oops
Leaving alt empty on every photo.
Do this instead
Write a short description like alt="Sunset at Baga beach". Use alt="" only for purely decorative images.
Oops
Building the modal from a div and forgetting Escape and focus.
Do this instead
Use the built-in <dialog> element. It handles Escape and keeps focus inside the modal.
Your mission
Milestone 1: grid to full screen
Quick check
You have 3 images and you're on index 0. You press Left. What is (current - 1 + images.length) % images.length?
Go deeper
- 1Build a responsive grid using CSS Grid's `auto-fill`/`auto-fit`. Keep the first version small, then add one improvement after the core behavior works.
- 2Add a modal lightbox that opens on click with next/prev navigation. Test the result on both a successful path and an invalid or unexpected input.
- 3Support closing the modal with the Escape key and clicking the backdrop.
More projects at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.