webdevpuneet.com

Image Lightbox — Free HTML CSS JS Gallery Snippet

Image Lightbox · Modals · Plain HTML, CSS & JS · Live preview

CategoryModals

Copy & paste

Source Code

<div class="gallery">
  <div class="grid">
    <div class="thumb" data-idx="0" style="background:linear-gradient(135deg,#6366f1,#a78bfa)">
      <span class="thumb-label">Mountain</span>
    </div>
    <div class="thumb" data-idx="1" style="background:linear-gradient(135deg,#ec4899,#f97316)">
      <span class="thumb-label">Sunset</span>
    </div>
    <div class="thumb" data-idx="2" style="background:linear-gradient(135deg,#10b981,#0ea5e9)">
      <span class="thumb-label">Forest</span>
    </div>
    <div class="thumb" data-idx="3" style="background:linear-gradient(135deg,#f59e0b,#ef4444)">
      <span class="thumb-label">Desert</span>
    </div>
    <div class="thumb" data-idx="4" style="background:linear-gradient(135deg,#8b5cf6,#ec4899)">
      <span class="thumb-label">Ocean</span>
    </div>
    <div class="thumb" data-idx="5" style="background:linear-gradient(135deg,#0ea5e9,#10b981)">
      <span class="thumb-label">City</span>
    </div>
  </div>
  <p class="hint">Click any image to open</p>
</div>

<!-- Lightbox overlay -->
<div class="lightbox" id="lb" role="dialog" aria-modal="true" aria-label="Image viewer">
  <button class="lb-close" id="lb-close" aria-label="Close">✕</button>
  <button class="lb-prev"  id="lb-prev"  aria-label="Previous">‹</button>
  <button class="lb-next"  id="lb-next"  aria-label="Next">›</button>

  <div class="lb-img" id="lb-img"></div>

  <div class="lb-footer">
    <span class="lb-caption" id="lb-cap"></span>
    <span class="lb-counter" id="lb-count"></span>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

3-column thumbnail grid with hover scale(1.03) and box-shadow lift
Gradient overlay on thumbnails via: :after for caption readability
Fullscreen overlay: position:fixed inset:0, display:none→flex via .open class
Fade-in animation: opacity 0→1 @keyframes on .open
min() sizing: width:min(600px,88vw) — capped on desktop, shrinks on mobile
Keyboard navigation: ArrowLeft, ArrowRight, Escape via document keydown listener
Keydown listener added on open and removed on close — no memory leaks
Click-outside close: e.target === e.currentTarget on overlay click handler
Modulo wrap: nav(dir) wraps from last to first and first to last

About this UI Snippet

Image Lightbox — Thumbnail Grid, Fullscreen Overlay, Keyboard Navigation & Click-Outside Close

Screenshot of the Image Lightbox snippet rendered live

An image lightbox opens a fullscreen overlay displaying a larger version of a clicked thumbnail, with navigation controls to move between images without closing the overlay. It is one of the most-used UI patterns on photography portfolios, e-commerce product galleries, and media-rich web pages. This snippet gives you a complete implementation: a 3-column responsive thumbnail grid, a fade-in fullscreen overlay, previous/next navigation, keyboard arrow key support, ESC to close, and click-outside-overlay close — all in plain HTML, CSS, and vanilla JavaScript.

How the overlay works

The lightbox is a position: fixed div that covers the full viewport (inset: 0). Its default display is none. Clicking a thumbnail calls open(idx) which sets the current index, calls show() to populate the image display, and adds the .open class — which switches display to flex. A @keyframes animation fades opacity from 0 to 1 on open. Closing removes .open, reverting display to none.

The image display

The snippet uses gradient placeholder blocks instead of real images so the lightbox works immediately without image files. In production, replace the gradient divs with img elements — set src from the images array. The .lb-img div uses width: min(600px, 88vw) and height: min(420px, 70vh) — the min() function caps the size for large screens while allowing it to shrink on mobile viewports.

Navigation

nav(dir) adds dir to the current index and wraps at both ends using modulo arithmetic: (cur + dir + images.length) % images.length. This ensures -1 from index 0 wraps to the last image and +1 from the last wraps to 0. The arrow buttons call nav(-1) and nav(1). The keyboard handler maps ArrowLeft and ArrowRight to the same calls.

Keyboard and click-outside close

The keyboard listener (document.addEventListener("keydown", onKey)) is added only when the lightbox opens and removed when it closes — preventing memory leaks. Click-outside is detected by checking e.target === e.currentTarget on the overlay div — clicking the overlay background fires the event on the overlay element itself, not on a child.

Replacing with real images

Update the images array: { label: "Caption text", src: "photo.jpg" }. In show(), set el.src = img.src on an img element inside .lb-img instead of setting background.

Step by step

How to Use

  1. 1
    Click any thumbnail to open the lightboxThe overlay fades in showing the selected image. Use the ‹ and › arrow buttons or keyboard arrow keys to navigate. Press ESC or click outside the image or click ✕ to close.
  2. 2
    Replace gradients with real imagesIn the images array, add a src property: { label: "Caption", src: "photo.jpg", bg: "" }. In the show() function, replace el.style.background = img.bg with an img tag: el.innerHTML = "<img src='"+img.src+"' style='max-width:100%;max-height:100%;border-radius:14px;' alt='"+img.label+"'>".
  3. 3
    Update the thumbnail gridReplace each .thumb div with an img tag or a real image background-image. Keep the onclick="open(N)" attribute on each thumbnail, incrementing N from 0. Keep the .thumb-label overlay text for caption display.
  4. 4
    Add more imagesAdd entries to the images array and add matching .thumb divs in the HTML with the correct onclick index. The navigation wraps automatically via modulo arithmetic.
  5. 5
    Change the overlay background opacityUpdate the background rgba on .lightbox from rgba(0,0,0,0.92) to your preferred darkness. For a blurred background (instead of black), add backdrop-filter: blur(20px) and reduce the rgba opacity to 0.5.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component using useState for the open state and current index, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Photography portfolios and image gallery pages
The lightbox pattern is the standard for photography portfolio sites — pair it with a photo gallery grid. The 3-column thumbnail grid with hover scale effect, gradient label overlay, and fullscreen viewer with keyboard navigation matches the expected pattern for professional image gallery pages.
E-commerce product image galleries
Wire the thumbnail grid to product images. Open the lightbox on thumbnail click to show a larger product view. Add a zoom-on-hover effect with the image magnifier snippet. Support swipe gestures by binding touchstart and touchend events to nav(), or present images in a scroll-snap gallery.
Blog post and article inline image viewers
Attach lightbox behaviour to article inline images: document.querySelectorAll("article img").forEach((img, i) => { img.style.cursor = "pointer"; img.onclick = () => open(i); }). Populate the images array from the src and alt attributes of each article img element.
Learn the click-outside detection and event lifecycle pattern
The e.target === e.currentTarget check for click-outside is a fundamental JavaScript pattern. The listener attach/remove on open/close pattern prevents memory leaks and shows the correct way to scope event listeners to component lifetime.
Document and attachment preview overlays
Adapt the lightbox for PDF previews, spreadsheet thumbnails, or design file previews. Replace the image display with an iframe or embed element. The overlay, navigation, keyboard, and close patterns work identically for any fullscreen preview content.
Team and conference speaker photo galleries
Use the grid + lightbox for team member photo galleries. Each lightbox panel can show the team member photo, name, role, and bio by populating a richer .lb-img content block instead of just the image.
Related: Recent Purchase Notification Popup
See the Recent Purchase Notification Popup for a related modals pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

The .lightbox div has an event listener: e.target === e.currentTarget. When the user clicks the dark overlay background, the click event fires on the .lightbox element itself (since nothing else is under the cursor). e.target is .lightbox and e.currentTarget is also .lightbox — they match, so close() fires. When the user clicks the image, the close button, or the nav buttons, e.target is that child element, not .lightbox — the condition is false and the lightbox stays open.

Add touchstart and touchend listeners to the .lb-img element. In touchstart, record startX = e.touches[0].clientX. In touchend, compute the delta: if (e.changedTouches[0].clientX - startX < -50) nav(1) for left swipe (next); if delta > 50, nav(-1) for right swipe (prev). A threshold of 50px prevents accidental swipes from tap jitter.

In the show() function, instead of setting the src immediately, set a loading state and use new Image() to preload: const preload = new Image(); preload.onload = () => { el.src = img.src; }; preload.src = img.src. Show a skeleton or spinner in .lb-img while the image loads. Also preload the next image in the background: const nextImg = images[(cur + 1) % images.length]; new Image().src = nextImg.src.

Click "JSX" to download. Use useState(null) for the open image index — null means closed. Conditionally render the lightbox: {openIndex !== null && <Lightbox .../>}. Pass onClose, onPrev, and onNext props. Add useEffect to attach and clean up the keydown listener when openIndex is not null: useEffect(() => { if (openIndex === null) return; const handler = e => {...}; document.addEventListener("keydown", handler); return () => document.removeEventListener("keydown", handler); }, [openIndex]).