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-fullscreenCommon 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
| Variant | Description |
|---|---|
| Basic Single Image | One 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 Navigation | Multiple 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 Lightbox | Extends 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 Lightbox | A 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">
‹
</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">
›
</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.
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.
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.