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.
Bootstrap class
.modalCommon 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 and classes
| Variant | Description and classes |
|---|---|
| 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.modal modal-dialog modal-dialog-centered modal-fullscreen-sm-down bg-black |
| 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.modal modal-dialog modal-xl modal-dialog-centered position-relative |
| 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.modal-footer bg-dark text-white border-0 small |
| 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.d-flex overflow-auto gap-2 p-2 bg-black |
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 template variants
Canvas Builder builds pages on the Canvas HTML template, which adds its own styled versions of Bootstrap 5 components. Variants you can ask for in a prompt:
- 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.