html|css|javascript 64 views

Responsive Image Gallery with CSS Grid and Modal

Create a responsive image gallery that opens images in a modal view with a smooth transition.

By TWC Team • Feb 02, 2026

Code

<!--
  Responsive Image Gallery + Modal (CSS Grid)
  Usage: Add images with data-modal="true" inside .gallery; click to open in an accessible modal.
-->
<div class="gallery" aria-label="Image gallery">
  <img src="image1.jpg" alt="Image 1" data-modal="true" loading="lazy" decoding="async">
  <img src="image2.jpg" alt="Image 2" data-modal="true" loading="lazy" decoding="async">
  <img src="image3.jpg" alt="Image 3" data-modal="true" loading="lazy" decoding="async">

  <div class="modal" id="imageModal" role="dialog" aria-modal="true" aria-hidden="true" aria-label="Image preview">
    <button class="close" type="button" aria-label="Close modal">&times;</button>
    <img class="modal-content" id="modalImg" alt="">
  </div>
</div>

<style>
  :root { color-scheme: light dark; }
  .gallery { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:10px; }
  .gallery img { width:100%; aspect-ratio:1/1; object-fit:cover; cursor:pointer; border-radius:10px;
    transition:transform .2s ease, filter .2s ease; will-change:transform; }
  .gallery img:hover { transform:scale(1.03); filter:saturate(1.05); }
  .gallery img:focus-visible { outline:3px solid CanvasText; outline-offset:3px; }

  .modal { position:fixed; inset:0; z-index:1000; display:flex; align-items:center; justify-content:center;
    padding:24px; background:rgba(0,0,0,.82); opacity:0; visibility:hidden; transition:opacity .18s ease, visibility .18s ease; }
  .modal.is-open { opacity:1; visibility:visible; }
  .modal-content { max-width:min(1100px,92vw); max-height:82vh; border-radius:14px; box-shadow:0 16px 60px rgba(0,0,0,.45);
    transform:translateY(8px) scale(.98); transition:transform .18s ease; }
  .modal.is-open .modal-content { transform:translateY(0) scale(1); }

  .close { position:absolute; top:14px; right:14px; width:44px; height:44px; border:0; border-radius:999px;
    background:rgba(255,255,255,.12); color:#fff; font-size:30px; line-height:44px; cursor:pointer; }
  .close:hover { background:rgba(255,255,255,.18); }
  .close:focus-visible { outline:3px solid #fff; outline-offset:3px; }

  @media (prefers-reduced-motion: reduce) {
    .gallery img, .modal, .modal-content { transition:none !important; }
  }
</style>

<script>
  // Modal behavior: event delegation (performance), ESC/backdrop close, focus restore, and safe image swapping.
  (() => {
    const modal = document.getElementById('imageModal');
    const modalImg = document.getElementById('modalImg');
    const closeBtn = modal.querySelector('.close');
    const gallery = document.querySelector('.gallery');
    if (!modal || !modalImg || !closeBtn || !gallery) return;

    let lastActiveEl = null;

    const openModal = (img) => {
      lastActiveEl = document.activeElement;
      modalImg.alt = img.alt || 'Selected image';
      modalImg.src = img.currentSrc || img.src; // supports responsive images if used later
      modal.classList.add('is-open');
      modal.setAttribute('aria-hidden', 'false');
      closeBtn.focus(); // basic focus management for accessibility
      document.documentElement.style.overflow = 'hidden'; // prevent background scroll
    };

    const closeModal = () => {
      modal.classList.remove('is-open');
      modal.setAttribute('aria-hidden', 'true');
      modalImg.removeAttribute('src'); // free memory for large images
      document.documentElement.style.overflow = '';
      lastActiveEl?.focus?.();
    };

    gallery.addEventListener('click', (e) => {
      const img = e.target.closest('img[data-modal="true"]');
      if (img) openModal(img);
    });

    closeBtn.addEventListener('click', closeModal);

    modal.addEventListener('click', (e) => {
      if (e.target === modal) closeModal(); // close on backdrop click only
    });

    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && modal.classList.contains('is-open')) closeModal();
    });
  })();

  // Example usage: Add more <img data-modal="true"> inside .gallery.
</script>
Back to Snippets