✦ A decade of Canvas craft, now driven by AI — describe it, watch it build live.Start building
Bootstrap 5Content

Bootstrap 5 Image Lightbox

An image lightbox is an overlay pattern that displays a full-size image above the page content when a thumbnail or trigger is activated. It is composed entirely from Bootstrap 5's Modal component, flexbox utilities, the ratio helper, and spacing classes — Bootstrap ships no official lightbox component. Use it whenever a gallery, portfolio, or product-image grid needs an enlarged-view experience without navigating away from the page.

Primary Class

.modal .modal-dialog .modal-fullscreen

Common Use Cases

  • A property-listing site shows a grid of room-photo thumbnails; clicking any thumbnail opens it at full viewport size inside a dark modal with a caption drawn from the alt text.
  • An e-commerce product page displays four colour-variant swatches in a row; tapping a swatch fires the lightbox to show a high-resolution studio shot of that variant.
  • A photographer's portfolio page renders a masonry-style grid; each card triggers the lightbox and overlays an arrow-navigation control so the visitor can step through the series without closing the modal.
  • A news article embeds several documentary photographs at reduced width inline; a lightbox lets readers enlarge each image to read fine detail without leaving the article flow.

Variants & Classes

VariantDescription
Basic Single ImageOne thumbnail linked to one full-size image via a data attribute; the modal centres the image with object-fit contain and a dark backdrop. Suitable for product shots or inline article images.
Gallery with Arrow NavigationMultiple thumbnails share one modal; previous/next buttons update the modal image src via a small inline script. Arrows are built from btn btn-outline-light positioned absolutely inside the modal-body.
Captioned LightboxExtends the basic modal with a modal-footer that shows the image title and description sourced from data-bs-caption on the trigger element. Uses text-white bg-dark for legibility over the dark backdrop.
Thumbnail Strip LightboxA scrollable strip of thumbnails sits inside modal-footer beneath the main image, letting users jump directly to any image in the set. The strip uses d-flex overflow-auto gap-2.

Code Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Bootstrap 5 Image Lightbox</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
  <style>
    .lb-thumb {
      cursor: pointer;
      transition: opacity .2s;
      width: 100%;
      height: 160px;
      object-fit: cover;
    }
    .lb-thumb:hover { opacity: .75; }
    #lightboxModal .modal-body {
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 60vh;
      background: #000;
      padding: 0;
    }
    #lb-main-img {
      max-height: 80vh;
      max-width: 100%;
      object-fit: contain;
    }
    .lb-prev, .lb-next {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      z-index: 10;
    }
    .lb-prev { left: 1rem; }
    .lb-next { right: 1rem; }
  </style>
</head>
<body class="bg-light">

<div class="container py-5">
  <h1 class="h4 mb-4">Property Photos</h1>
  <div class="row g-3">
    <div class="col-6 col-md-3">
      <img src="https://picsum.photos/seed/room1/800/600"
           class="lb-thumb rounded"
           data-bs-toggle="modal"
           data-bs-target="#lightboxModal"
           data-lb-src="https://picsum.photos/seed/room1/1600/1200"
           data-lb-caption="Living Room"
           alt="Living Room">
    </div>
    <div class="col-6 col-md-3">
      <img src="https://picsum.photos/seed/room2/800/600"
           class="lb-thumb rounded"
           data-bs-toggle="modal"
           data-bs-target="#lightboxModal"
           data-lb-src="https://picsum.photos/seed/room2/1600/1200"
           data-lb-caption="Master Bedroom"
           alt="Master Bedroom">
    </div>
    <div class="col-6 col-md-3">
      <img src="https://picsum.photos/seed/room3/800/600"
           class="lb-thumb rounded"
           data-bs-toggle="modal"
           data-bs-target="#lightboxModal"
           data-lb-src="https://picsum.photos/seed/room3/1600/1200"
           data-lb-caption="Kitchen"
           alt="Kitchen">
    </div>
    <div class="col-6 col-md-3">
      <img src="https://picsum.photos/seed/room4/800/600"
           class="lb-thumb rounded"
           data-bs-toggle="modal"
           data-bs-target="#lightboxModal"
           data-lb-src="https://picsum.photos/seed/room4/1600/1200"
           data-lb-caption="Garden"
           alt="Garden">
    </div>
  </div>
</div>

<!-- Lightbox Modal -->
<div class="modal fade" id="lightboxModal" tabindex="-1" aria-label="Image lightbox" aria-modal="true" role="dialog">
  <div class="modal-dialog modal-xl modal-dialog-centered">
    <div class="modal-content bg-black border-0">

      <!-- Header -->
      <div class="modal-header border-0 pb-0">
        <h2 class="modal-title text-white fs-6" id="lb-caption-text"></h2>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>

      <!-- Body -->
      <div class="modal-body position-relative">
        <button class="lb-prev btn btn-outline-light btn-sm" aria-label="Previous image">
          &#8249;
        </button>
        <img id="lb-main-img" src="" alt="" class="d-block mx-auto">
        <button class="lb-next btn btn-outline-light btn-sm" aria-label="Next image">
          &#8250;
        </button>
      </div>

      <!-- Thumbnail strip -->
      <div class="modal-footer border-0 bg-black p-2">
        <div class="d-flex overflow-auto gap-2 w-100" id="lb-thumb-strip"></div>
      </div>

    </div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
  (function () {
    const thumbs = Array.from(document.querySelectorAll('.lb-thumb'));
    const mainImg = document.getElementById('lb-main-img');
    const captionEl = document.getElementById('lb-caption-text');
    const strip = document.getElementById('lb-thumb-strip');
    const modal = document.getElementById('lightboxModal');
    let current = 0;

    // Build thumbnail strip
    thumbs.forEach(function (th, i) {
      const s = document.createElement('img');
      s.src = th.src;
      s.alt = th.alt;
      s.className = 'rounded flex-shrink-0';
      s.style.cssText = 'height:56px;width:80px;object-fit:cover;cursor:pointer;opacity:.6;transition:opacity .2s';
      s.addEventListener('click', function () { show(i); });
      strip.appendChild(s);
    });

    function show(index) {
      current = index;
      const th = thumbs[current];
      mainImg.src = th.dataset.lbSrc || th.src;
      mainImg.alt = th.alt;
      captionEl.textContent = th.dataset.lbCaption || th.alt;
      // Highlight active strip thumb
      strip.querySelectorAll('img').forEach(function (s, i) {
        s.style.opacity = i === current ? '1' : '.6';
        s.style.outline = i === current ? '2px solid #fff' : 'none';
      });
    }

    // Open on thumbnail click
    modal.addEventListener('show.bs.modal', function (e) {
      const trigger = e.relatedTarget;
      if (trigger && trigger.classList.contains('lb-thumb')) {
        show(thumbs.indexOf(trigger));
      }
    });

    // Arrow navigation
    document.querySelector('.lb-prev').addEventListener('click', function () {
      show((current - 1 + thumbs.length) % thumbs.length);
    });
    document.querySelector('.lb-next').addEventListener('click', function () {
      show((current + 1) % thumbs.length);
    });

    // Keyboard navigation
    modal.addEventListener('keydown', function (e) {
      if (e.key === 'ArrowLeft') { show((current - 1 + thumbs.length) % thumbs.length); }
      if (e.key === 'ArrowRight') { show((current + 1) % thumbs.length); }
    });
  })();
</script>
</body>
</html>

Live Examples

Basic Single-Image Lightbox

A single thumbnail that opens a larger version of itself in a centred modal. No JavaScript navigation logic needed — the modal's relatedTarget supplies the high-res src from a data attribute.

Example 1

Captioned Lightbox with Dark Footer

Each thumbnail carries a data-lb-caption attribute. On modal open, the caption populates a dark modal-footer below the image, mimicking a traditional lightbox caption bar.

Example 2

Canvas Framework Variants

The Canvas template extends Bootstrap 5 with 1,658+ component variants. Generate any of these using Canvas Builder:

  • Single-image lightbox with close button and dark backdrop
  • Gallery lightbox with previous/next arrow navigation and keyboard support
  • Captioned lightbox with a dark footer bar populated from thumbnail alt text
  • Thumbnail-strip lightbox where clicking any strip item jumps to that image
  • Canvas Builder can generate a fully wired multi-image lightbox from a plain-text prompt describing your gallery layout

Best Practices

Always set aria-modal and role on the modal element

Bootstrap's modal already handles focus-trap, but adding aria-modal="true" and role="dialog" explicitly ensures screen readers announce the overlay correctly. Pair this with a descriptive aria-label rather than relying on a hidden aria-labelledby heading that may not be visible.

Use object-fit: contain on the full-size image, not cover

Inside a lightbox modal-body, object-fit: contain ensures the entire image is visible regardless of its aspect ratio, preventing unwanted cropping. Set max-height to something like 80vh alongside max-width: 100% so portrait images do not overflow the viewport vertically.

Lazy-load high-res sources to avoid wasted bandwidth

Store the high-res URL in a data-lb-src attribute on the thumbnail rather than in a hidden img tag. Assign it to the modal image src only when the modal opens — via the show.bs.modal event — so visitors who never open the lightbox never download the large file.

Prevent background scroll during lightbox display

Bootstrap's modal automatically adds overflow: hidden to the body when open, which stops background scroll without custom CSS. If you are using a fullscreen modal variant (modal-fullscreen), verify the body padding-right compensation is not shifting your fixed header by auditing your layout at different viewport widths.

FAQ

Does Bootstrap 5 include a built-in lightbox component?
No. Bootstrap 5 ships no official lightbox. This pattern composes Bootstrap's Modal component, flexbox utilities (d-flex, align-items-center, justify-content-center), colour utilities (bg-black, text-white), and a small inline script to swap image sources. No third-party library is required.
How do I add keyboard arrow navigation to the lightbox?
Attach a keydown listener to the modal element itself (not document, to avoid conflicts) and check for ArrowLeft and ArrowRight keys. Because Bootstrap moves focus into the modal on open, the modal element receives keyboard events without extra configuration. See the main code example for the exact implementation.
Can I use the lightbox with dynamically loaded images, such as those fetched from an API?
Yes. Generate the thumbnail img elements with data-lb-src and data-lb-caption attributes at render time, then call the show() helper function directly with the appropriate index. If thumbnails are injected after page load, re-query the .lb-thumb NodeList inside the show.bs.modal handler rather than caching it on page load.
Why does the modal shift the page layout when it opens?
Bootstrap adds a right padding to the body equal to the scrollbar width to prevent layout shift when overflow: hidden is applied. If this causes a visible jump in a fixed navbar, add padding-right: var(--bs-modal-padding) (or the computed scrollbar width) to the navbar in a style rule scoped to the .modal-open body state, or use Bootstrap's built-in --bs-scrollbar-width CSS variable available in 5.3.