Step 4.2 · Gallery pages

Add the lightbox (JavaScript)

Connect gallery.js so thumbnails open in an accessible lightbox with previous, next and close controls.

You will work in
site/js/gallery.js, each gallery page
Learning outcome
Interactive galleries with JavaScript

JavaScript is the third language of the web:

The lightbox uses the browser's built-in <dialog> element. It handles keyboard focus and the Esc key for you, and it needs no plug-ins.

  1. Check that gallery.js is in your js folder.

  2. Check the link on each gallery page, just before </body>. The starter gallery page already has it, so your copies do too. Scripts at the bottom run after the gallery HTML has loaded.

    site/red.html
      <!-- Lightbox script: keep this just before </body> -->
      <script src="js/gallery.js"></script>
    </body>
  3. Test it. Click a thumbnail, use the arrows, close it. Then do it all with the keyboard only: Tab to a thumbnail, Enter to open, ← → to move, Esc to close.

  4. Read the script and add your own comment to each numbered part explaining what it does in your words.

The script, explained

site/js/gallery.js
// =========================================================
// gallery.js — opens gallery thumbnails in a lightbox
// Uses the browser's built-in <dialog> element. No jQuery needed.
// Link this file at the bottom of each gallery page:
//   <script src="js/gallery.js"></script>
// =========================================================

// 1. Find every link inside the gallery list.
const links = Array.from(document.querySelectorAll('.gallery a'));

// Only run the rest if this page has a gallery.
if (links.length > 0) {

  // 2. Build the lightbox once and add it to the page.
  const lightbox = document.createElement('dialog');
  lightbox.className = 'lightbox';
  lightbox.setAttribute('aria-label', 'Image viewer');
  lightbox.innerHTML = `
    <figure>
      <img src="" alt="">
      <figcaption></figcaption>
    </figure>
    <button class="lb-prev" type="button" aria-label="Previous image">&#8249;</button>
    <button class="lb-next" type="button" aria-label="Next image">&#8250;</button>
    <button class="lb-close" type="button" aria-label="Close">&times;</button>
  `;
  document.body.append(lightbox);

  // 3. Keep references to the parts we will change.
  const bigImage = lightbox.querySelector('img');
  const caption = lightbox.querySelector('figcaption');
  let current = 0;   // which image is showing (0 = the first one)

  // 4. Show image number "index" in the lightbox.
  function showImage(index) {
    // Wrap around: after the last image, go back to the first.
    current = (index + links.length) % links.length;

    const link = links[current];
    const thumb = link.querySelector('img');

    bigImage.src = link.href;          // the large image file
    bigImage.alt = thumb.alt;          // reuse the thumbnail's alt text
    caption.textContent =
      (link.dataset.caption || thumb.alt) + '  (' + (current + 1) + ' of ' + links.length + ')';
  }

  // 5. Clicking a thumbnail opens the lightbox instead of a new page.
  links.forEach(function (link, index) {
    link.addEventListener('click', function (event) {
      event.preventDefault();
      showImage(index);
      lightbox.showModal();
    });
  });

  // 6. Buttons.
  lightbox.querySelector('.lb-prev').addEventListener('click', function () {
    showImage(current - 1);
  });
  lightbox.querySelector('.lb-next').addEventListener('click', function () {
    showImage(current + 1);
  });
  lightbox.querySelector('.lb-close').addEventListener('click', function () {
    lightbox.close();
  });

  // 7. Keyboard: arrow keys change images. (Esc closes a dialog automatically.)
  lightbox.addEventListener('keydown', function (event) {
    if (event.key === 'ArrowLeft') showImage(current - 1);
    if (event.key === 'ArrowRight') showImage(current + 1);
  });

  // 8. Clicking the dark backdrop closes the lightbox.
  lightbox.addEventListener('click', function (event) {
    if (event.target === lightbox) lightbox.close();
  });

  // 9. When it closes, put keyboard focus back on the thumbnail.
  lightbox.addEventListener('close', function () {
    links[current].focus();
  });
}
PartWhat happens
1querySelectorAll('.gallery a') finds every link in the gallery, like a CSS selector.
2Creates the <dialog> with an image, caption and three buttons, and adds it to the page.
3Stores the parts that change, and current remembers which image is showing.
4A function is a reusable set of instructions. showImage swaps in the large image, alt text and caption. The % math wraps from the last image back to the first.
5An event listener waits for a click. preventDefault() stops the link opening a new page; showModal() opens the dialog.
6–8Buttons, arrow keys and clicking the dark backdrop.
9On close, keyboard focus returns to the thumbnail so keyboard users don't lose their place.

Style the lightbox

The lightbox's look is in section 9 of styles.css. Change the backdrop color, caption font and buttons to match your design.

site/css/styles.css · section 9
/* ===== 9. LIGHTBOX (used by js/gallery.js) ===== */
.lightbox {
  border: 0;
  padding: 0;
  background: transparent;
  max-width: 92vw;
  max-height: 92vh;
  overflow: visible;
}

.lightbox::backdrop {
  background-color: rgba(0, 0, 0, 0.88);
}

.lightbox img {
  max-height: 80vh;
  width: auto;
  margin: 0 auto;
}

.lightbox figure {
  margin: 0;
}

.lightbox figcaption {
  color: #ffffff;
  text-align: center;
  padding: 12px 56px;
}

.lightbox button {
  position: fixed;
  width: 48px;
  height: 48px;
  font-size: 2rem;
  line-height: 1;
  color: #ffffff;
  background-color: rgba(0, 0, 0, 0.6);
  border: 2px solid #ffffff;
  border-radius: 50%;
  cursor: pointer;
}

.lightbox button:hover,
.lightbox button:focus-visible {
  color: #000000;
  background-color: #ffffff;
}

.lb-close { top: 16px; right: 16px; }
.lb-prev  { top: 50%; left: 12px; }
.lb-next  { top: 50%; right: 12px; }

Stretch options

Choose one for bonus credit:

Learn more

Look up any part of the script in the W3Schools JavaScript tutorial, for example the dialog element or addEventListener.

© Created by Sara Rosenstock, Marietta College Graphic Design.