A responsive image gallery with a click-to-open lightbox overlay, prev/next navigation, and keyboard support.
Thumbnails in a CSS grid, plus a hidden lightbox overlay.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Gallery</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php require_once __DIR__ . '/../promo_banner.php'; ?>
<h1>Gallery</h1>
<div class="gallery" id="gallery">
<!-- JS will inject thumbnails -->
</div>
<!-- Lightbox -->
<div class="lightbox hidden" id="lightbox">
<button class="lb-close" onclick="closeLb()">✕</button>
<button class="lb-prev" onclick="step(-1)">←</button>
<img id="lb-img" src="" alt="">
<button class="lb-next" onclick="step(1)">→</button>
</div>
<script src="gallery.js"></script>
</body>
</html>
Build the grid dynamically and handle lightbox navigation.
const images = [
'https://picsum.photos/seed/a/600/400',
'https://picsum.photos/seed/b/600/400',
'https://picsum.photos/seed/c/600/400',
'https://picsum.photos/seed/d/600/400',
'https://picsum.photos/seed/e/600/400',
'https://picsum.photos/seed/f/600/400',
];
let current = 0;
// Render thumbnails
document.getElementById('gallery').innerHTML = images.map((src, i) =>
`<img src="${src}" class="thumb" onclick="openLb(${i})" loading="lazy" alt="">`
).join('');
function openLb(i) {
current = i;
document.getElementById('lb-img').src = images[i];
document.getElementById('lightbox').classList.remove('hidden');
}
function closeLb() { document.getElementById('lightbox').classList.add('hidden'); }
function step(dir) {
current = (current + dir + images.length) % images.length;
document.getElementById('lb-img').src = images[current];
}
document.addEventListener('keydown', e => {
if (e.key === 'Escape') closeLb();
if (e.key === 'ArrowLeft') step(-1);
if (e.key === 'ArrowRight') step(1);
});
Your gallery has a fully working lightbox with keyboard nav. Swap picsum.photos for your own images and add it to your portfolio.
Spotted a bug, broken code, or something that doesn't look right? Tell us what's off and we'll fix it.