What Is a Bootstrap Gallery?
Nothing inside the Bootstrap framework carries the name, so it works as a pattern more than a package. Thumbnails sit in a responsive grid built from Bootstrap’s classes, and each one opens its full-size image in a modal, a carousel or a lightbox plugin.
Bootstrap 5 also ships without a lightbox, so you either add a third-party library or build on the modal. The second route builds the lightbox on Bootstrap’s own modal with no external dependency.
The grid and thumbnail side uses container, row, col-* or row-cols-*, img-fluid and the ratio helper, with a card or figure around each image if it needs a caption. The viewer is separate: the modal, the carousel, or a third-party lightbox.
Every one of those parts is among the stock Bootstrap components, so a gallery adds no dependency until you pick a lightbox library.
The University of Arizona packages the idea as a Photo Gallery component in Arizona Bootstrap. It sits on Bootstrap’s carousel and modal and offers grid and slider display options. Its docs warn that the markup changed because of breaking changes to carousels and modals in Bootstrap 5.
Watch the version on anything you find in search results. The blueimp Bootstrap Image Gallery plugin lists Bootstrap 3.3.6+ and jQuery 1.11.3+ as requirements (blueimp README). That puts it in the Bootstrap 3 era, so skip it for a Bootstrap 5 build.
As of October 2026, Bootstrap 6.0.0 Alpha 1 (released October 8, 2026) is out with ESM-only JavaScript and renamed classes, while Bootstrap 5 stays supported, with v5.4.0 still planned before an end-of-life date is announced (Bootstrap blog). I’d treat the alpha as a test build for now.
The markup in this article targets Bootstrap 5.3.
How Do Grid Classes Control Gallery Columns?
Set the column count once on the row with row-cols-* classes, then add breakpoint variants so the count rises with screen width. Gutter classes (g-*) set the spacing between thumbnails.
A typical gallery row reads row row-cols-2 row-cols-md-3 row-cols-lg-4 g-3. That gives two thumbnails per row on phones, three from 768 px and four from 992 px.
Each thumbnail then sits in a plain col. Bootstrap’s grid documentation describes row-cols-* as a shortcut set on the parent row, and it beats repeating col-md-4 on every item once the gallery grows past a handful of images.
Under the hood, each thumbnail takes an equal share of the 12-column grid system. With row-cols-4, every item gets the width of three of the twelve columns.
The grid has six breakpoint tiers (xs, sm, md, lg, xl and xxl). The sm tier starts at 576 px, md at 768 px and lg at 992 px, with xl at 1200 px and xxl at 1400 px (Bootstrap 5.3 grid documentation).
These tiers are min-width media queries. A class like row-cols-md-3 applies at md and every tier above it, until a larger tier overrides it.
The g-* classes use the same sizes as margin and padding spacing, so g-3 is a mid-size gap and g-0 removes it.
Images with different aspect ratios leave ragged rows, and grid classes cannot fix that on their own.
How Do You Keep Thumbnails the Same Size and Add Captions?
Wrap each thumbnail in a fixed-ratio box and crop the image to fill it. The caption then goes in a figure element or a card body, depending on how much text each item carries.
Equal Sizes With the Ratio Helper and object-fit
The img-fluid class sets max-width to 100% and height to auto, so an image scales with its column but keeps its own proportions (Bootstrap 5.3 images documentation). Alone, that leaves uneven rows whenever source files differ.
The ratio helper fixes the box instead. Its default classes are ratio-1×1, ratio-4×3, ratio-16×9 and ratio-21×9, and the docs confirm it works on any child element, an img included.
Locking the aspect ratio of the wrapper also reserves the thumbnail’s space before the file loads. A custom value comes from the –bs-aspect-ratio variable, for example 50% for 2×1.
The wrapper is a div with ratio and ratio-4×3. The image goes in as a direct child, which the ratio class sizes automatically, and it takes the object-fit-cover utility class (added in Bootstrap 5.3) to apply object-fit: cover. If you want a frame around it, img-thumbnail adds a rounded 1px border with a little padding and a background.
Cropping has a cost, though. object-fit: cover cuts the edges, so a portrait photo in a 4×3 box loses its top and bottom. Pick the ratio that fits most of the set, or switch to object-fit: contain (the object-fit-contain class) on a neutral background when nothing can be cropped.
Card or Figure for Captions
Both work. It comes down to how much text each thumbnail carries.
A figure pairs the image and caption through figure and figcaption, using the classes figure, figure-img and figure-caption. You add img-fluid by hand, since images in figures have no explicit size (Bootstrap docs).
A card puts card-img-top on the image and the title and text in card-body. Add h-100 to the card and heights equalize across a row.
A Bootstrap card costs more markup per item, but it brings padding, a border and room for a title, a date or a link. For a plain photo gallery, figure is the lighter choice.
There’s also the hover overlay, which sits on the image with absolute positioning. Touch screens have no hover, so show the caption by default on small screens.
The Arizona Photo Gallery sidesteps the problem by keeping captions off the image in its grid viewer (University of Arizona Digital docs).
Modal, Carousel or Lightbox Library: Which Full-Size Viewer Fits?
Use one Bootstrap modal for small galleries that only need enlarge and close. Add a carousel inside it for previous and next, and move to a lightbox library once the gallery needs zoom or video.
| Viewer | Needs | Strength | Constraint |
|---|
| Modal with carousel | Bootstrap bundle only | No extra download, touch swipe | No built-in zoom |
| GLightbox | Nothing, vanilla JS | Images, video, iframes, zoom option | Extra asset, about 11 KB gzipped |
| Lightbox2 | jQuery 1.7+ | Original lightbox script, MIT license | jQuery slim build unsupported |
| Fancybox | jQuery (v3) | Touch enabled, responsive | Commercial license for commercial sites |
| PhotoSwipe v5 | Core, Lightbox and CSS files | Touch gestures, modular | Width and height required per image |
The table draws on the Bootstrap carousel docs, the GLightbox npm page, the Lightbox2 project page, the fancyBox README and the PhotoSwipe getting-started docs.
Bootstrap’s own modal documentation lists lightboxes among its intended uses, so a single Bootstrap modal reused for every thumbnail is a legitimate default, not a workaround.
The cleanest setup keeps one dialog in the DOM and has each thumbnail pass its image index through a data attribute. The alternative is one modal per image, which drags a copy of the dialog markup along with every thumbnail, and each copy needs its own id.
Navigation between images comes from a Bootstrap carousel placed in the modal body. It gives arrows, indicators and swipe without a plugin.
My rule of thumb is that under roughly 12 images with no zoom, the plain modal is enough. That threshold is an editorial guideline, not a Bootstrap limit.
Check licenses before choosing. As of October 2026, Lightbox2 and GLightbox are MIT licensed, while fancyBox 3’s README requires a commercial license for commercial applications and the later fancyapps/ui releases fall under the separate Fancyapps UI license agreement.
PhotoSwipe’s docs say every image needs predefined width and height. That is a constraint, but it also keeps layout shift out of the gallery.
How to Build the Modal and Carousel Viewer Step by Step
Bootstrap’s data attributes open the modal, and a short script keeps the carousel on the right slide. Everything else is markup.
- Load the Bootstrap 5.3 CSS and bootstrap.bundle.min.js from jsDelivr or npm. The bundle includes Popper and needs no jQuery (Bootstrap README).
- Build the thumbnail grid from the grid section. Make each thumbnail a button with
data-bs-toggle="modal", data-bs-target="#galleryModal" and a data-index holding its position, starting at 0. - Add one modal with the classes modal and fade, tabindex=”-1″ on the outer element, and a btn-close button with
data-bs-dismiss="modal". The tabindex lets focus enter the modal and Escape close it. - Inside the modal, add a carousel with one carousel-item per image. Mark exactly one item active, give each img d-block and w-100, and leave out data-bs-ride.
- Add previous and next buttons with
data-bs-slide, each with a visually-hidden text label. - Add the sync script below so the carousel jumps to the clicked thumbnail when the modal opens.
- Test every thumbnail: start slide, Escape to close, arrow keys between slides.
This is the markup for one thumbnail and the modal.
<button type="button" class="p-0 border-0 bg-transparent" data-bs-toggle="modal" data-bs-target="#galleryModal" data-index="0">
<img src="thumb-1.jpg" class="img-fluid" alt="Short description of image 1">
</button>
<div class="modal fade" id="galleryModal" tabindex="-1" aria-label="Image viewer">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<button type="button" class="btn-close ms-auto m-2" data-bs-dismiss="modal" aria-label="Close"></button>
<div id="galleryCarousel" class="carousel slide">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="full-1.jpg" class="d-block w-100" alt="Short description of image 1">
</div>
<!-- one carousel-item per image -->
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#galleryCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#galleryCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
</div>
</div>
</div>
The sync script is short.
const modalEl = document.getElementById('galleryModal');
const carouselEl = document.getElementById('galleryCarousel');
const carousel = bootstrap.Carousel.getOrCreateInstance(carouselEl);
modalEl.addEventListener('show.bs.modal', (event) => {
carousel.to(Number(event.relatedTarget.dataset.index));
});
Pass { wrap: false } as the second argument to getOrCreateInstance if the viewer should stop at the first and last image.
Autoplay is the mistake that bites most often. data-bs-ride="carousel" starts cycling on load (the value true waits for the first interaction), and Bootstrap’s carousel documentation points to WCAG 2.2 criterion 2.2.2 (Pause, Stop, Hide) for autoplaying carousels. A viewer should not cycle by itself.
Skip the sync script and the carousel opens on whichever item carries the active class, not the thumbnail that was clicked. A carousel with no active slide renders nothing at all. And if two galleries share a page, each needs its own modal and carousel ids.
How Do You Build a Masonry Gallery in Bootstrap 5?
Bootstrap 5 has no masonry layout of its own. Add the Masonry library to the grid row, or approximate the effect with the CSS multi-column layout.
The change is documented. Bootstrap 5 removed .card-columns in favor of Masonry and moved the demo into its examples section (Bootstrap pull request #28922).
| Method | JavaScript | Item order | Constraint |
|---|
| Masonry library | Yes | Shortest column first | Needs image sizes at layout time |
| CSS multi-column layout | No | Down each column | Visual order differs from DOM order |
| CSS grid-lanes | No | Fills the emptiest lane | Safari 26.4 only, as of October 2026 |
Bootstrap’s own Masonry example loads masonry-layout 4.2.2 from a CDN and puts data-masonry='{"percentPosition": true }' on the row. The columns keep their Bootstrap classes, and Masonry only repositions them.
The library measures each item before it places it, and an image that has not loaded has no measurable size (Metafizzy, Masonry’s maker). You see it as overlapping items. The Masonry FAQ names imagesLoaded as the fix, which runs the layout once the images report in.
Width and height attributes on each img also give an item a measurable height before the file arrives, so they belong in a masonry grid too.
CSS multi-column layout needs no script. Set column-count or column-width on a wrapper and let the items flow. Content runs down the first column and then into the next (MDN), so the order a keyboard or screen reader user follows no longer matches what the eye reads across.
Support is not the problem, since multi-column layout has been available across browsers since March 2017 (MDN). Add break-inside: avoid to each item so a card does not split across two columns, and see this guide to CSS masonry techniques for the pure-CSS variants.
The newest option is display: grid-lanes, which packs items into whichever lane has the most free space. Safari 26.4, released on 2026-03-24, is the only browser listed as supporting it (Web Platform DX feature explorer). Wrap it in @supports and keep a normal grid underneath.
For a Bootstrap gallery, the Masonry library is still the practical pick. CSS columns work when reading order does not matter, and grid-lanes is an enhancement for now, not a base layer.
How to Filter a Bootstrap Gallery by Category
Tag each gallery item with a data-category attribute, wire category buttons to show or hide matching items, and let the row reflow. Add Isotope only when you also want animated reflow, sorting or combined filters.
The plain version puts data-category="nature" on each col and adds one button per category plus an All button, grouped in a btn-group. A short loop over the items toggles d-none whenever the category does not match.
The grid is built on flexbox, so hiding a col with d-none closes the gap by itself. Build the controls from Bootstrap’s button classes and mark the selected one with active.
With Masonry in the mix, call its layout method after every toggle, because the library positions items itself.
Isotope filters and sorts with animated reflow, and its masonry layout mode is built in. Its license page lists GPLv3 as the open source license and points commercial sites, themes and applications to a paid commercial license (checked October 2026).
That license cost is the main reason to start with the plain approach. A category filter over a few dozen items is a short loop.
Filtering does break the viewer, though. The carousel inside the modal still holds every slide, so if you filter the grid to one category, open an image and press next, the carousel steps into images the visitor filtered out.
Rebuild the carousel items from the visible thumbnails whenever the filter changes, then renumber data-index so each thumbnail still points at its own slide.
Hidden is not removed, either. Eager images in a filtered-out category still download, so keep loading=”lazy” on every thumbnail outside the first row.
How to Make a Bootstrap Gallery Load Fast
Most of the speed comes from lazy loading below the first row, explicit image dimensions, responsive sources in modern formats, and separate thumbnail and full-size files.
Lazy Loading Without Hurting LCP
In HTTP Archive data analyzed by Google’s web.dev team, pages using browser-level lazy loading had a median 75th percentile LCP of 3,546 ms, against 2,922 ms for pages without it.
That is correlation, and the article says so. It also calls lazy loading an effective way to cut unneeded image bytes while warning that overuse hurts performance, which is the tension a gallery has to manage.
Leave the first row eager, with no loading attribute. If one image is the largest above the fold, give it fetchpriority=”high” and never lazy-load it. Everything else gets loading=”lazy”, the full-size slides in the modal included.
Shopify’s theme performance best practices put “never lazy-load the LCP image” among the essential, high-impact rules for themes. A gallery that puts loading=”lazy” on every thumbnail breaks it.
Layout Shift and Reserved Space
Reserve the space or the page jumps.
web.dev’s layout shift guidance says to put width and height attributes on every image and video, or to reserve the space with CSS aspect-ratio (updated February 2025). Google’s Core Web Vitals thresholds rate a CLS of 0.1 or less as good.
The ratio helper already reserves each thumbnail’s box, so the attributes matter most on carousel slides and on any image outside a ratio wrapper.
A neutral background on the ratio box, or skeleton screens for larger sets, gives the grid something to show while files arrive.
Responsive Sources and Modern Formats
<picture>
<source type="image/avif" srcset="thumb-480.avif 480w, thumb-800.avif 800w" sizes="(min-width: 992px) 25vw, (min-width: 768px) 33vw, 50vw">
<source type="image/webp" srcset="thumb-480.webp 480w, thumb-800.webp 800w" sizes="(min-width: 992px) 25vw, (min-width: 768px) 33vw, 50vw">
<img src="thumb-480.jpg" srcset="thumb-480.jpg 480w, thumb-800.jpg 800w" sizes="(min-width: 992px) 25vw, (min-width: 768px) 33vw, 50vw" width="800" height="600" class="img-fluid" loading="lazy" alt="Short description">
</picture>
The sizes values follow the row-cols example: 50vw on phones, 33vw from md, 25vw from lg. Bootstrap’s docs say to put img-* classes on the img, not on the picture element, and the img should keep a JPEG fallback for browsers that skip both sources.
Serve full-size files only to the viewer, so 40 thumbnails never pull 40 large photos.
How to Make a Bootstrap Gallery Accessible
Write alt text that describes each image’s content, build thumbnails as buttons or links, and test the viewer with a keyboard alone. Focus moves into the modal on open, stays inside, and returns to the clicked thumbnail on close.
Basic web accessibility work on a gallery starts with alt text, which is where most sites fail. WebAIM’s 2026 Million report found missing alt text on 53.1% of the top million home pages, and 10.8% of images that did have alt text carried questionable or repetitive text, such as a file name or a copy of the adjacent text.
For a gallery, that means describing what the image shows, not that it is an image. Don’t repeat the figcaption word for word, since screen readers read both. Use alt=”” only for purely decorative images, and give the full-size slide the same alt as its thumbnail.
On focus and naming, the W3C’s WAI-ARIA Authoring Practices say a modal dialog contains its Tab sequence, returns focus to the invoking element on close, and needs a name from aria-labelledby or aria-label.
Bootstrap’s modal script adds the dialog role and moves focus itself (Bootstrap documentation). That works only if thumbnails are real buttons, because a bare img with a click handler gives focus nothing to return to.
The carousel pattern in the same guide groups each slide with aria-roledescription=”slide” and a label such as “3 of 12”, so screen reader users know where they are. The stock Bootstrap carousel markup carries no slide labels, so add them once the gallery grows past a handful of images.
Use ARIA to fill gaps, not to replace native elements. WebAIM found that home pages with ARIA present averaged 59.1 errors against 42 for pages without it, which is correlation, but it matches the advice to prefer button and a elements over divs with roles.
Then run a keyboard test.
- Tab to a thumbnail, press Enter, and check that focus lands inside the modal
- Press Tab repeatedly and confirm focus stays inside until the modal closes
- Press Escape and confirm focus returns to the same thumbnail
- Run NVDA or VoiceOver over one open slide and confirm the alt text and position are announced
When a Bootstrap Gallery Is the Wrong Tool
The native setup gets awkward in a few places, and each one has a workaround.
Large image sets hit a limit first. Lighthouse warns when the body holds more than about 800 nodes and reports an error above about 1,400 (Chrome for Developers).
In the markup built in this article, every image costs six elements: a col, a ratio box, a button and an img for the thumbnail, plus a carousel-item and an img for the slide. That puts the 800-node warning near 133 images, before the header and footer count.
Chrome’s guidance is to create nodes only when they are needed. Load thumbnails in batches with an Ajax request, and build carousel slides when the modal opens.
Product galleries are a different animal. A store page switches one large main image from a thumbnail strip and needs zoom, which is not the same pattern as a grid that opens a viewer. Start from a library built for that job instead of bending the grid.
Version mismatches cause the quietest failures. Bootstrap 4 triggers used data-toggle and data-target, while Bootstrap 5 uses data-bs-toggle and data-bs-target (compare the 4.0 and 5.3 modal docs). A plugin written for the old attributes opens nothing on Bootstrap 5, and Bootstrap 5 itself no longer needs jQuery, so a jQuery-based plugin adds a dependency you would otherwise skip.
The grid also fixes column widths, so it cannot produce Flickr-style rows where every image has the same height and the row fills the full width exactly. A justified-layout script, or a flex layout driven by each image’s aspect ratio, does that job.
One more case. If the site does not already load Bootstrap, pulling in a framework for one component is the heavier choice, and a plain CSS gallery covers a grid with hover effects on its own.
If two or more of these describe your project, skip the native modal and plan around a dedicated gallery library from the start.
Bootstrap Gallery FAQ
Is jQuery required for a Bootstrap 5 gallery?
No. Bootstrap 5 dropped its jQuery dependency, and the bundle file includes Popper, so the grid, modal and carousel run without it.
The only script a modal-and-carousel gallery needs is the short plain-JavaScript sync handler from the build steps.
Can I reuse Bootstrap 4 gallery code in Bootstrap 5?
Not unchanged. Bootstrap 5 renamed .embed-responsive to .ratio, replaced .no-gutters with .g-0, and removed .card-columns.
Scan a Bootstrap 4 snippet for those three classes first, then check its modal and carousel attributes.
Can a Bootstrap gallery show videos as well as images?
Yes. Put the video embed in a modal and wrap the iframe in the ratio helper, for example ratio ratio-16×9, so it keeps its proportions.
For a mixed set, a lightbox library such as GLightbox opens images, YouTube, Vimeo and self-hosted video from one gallery.
How do I center an incomplete last row?
Add justify-content-center to the row. Flexbox then centers the leftover thumbnails instead of leaving them against the left edge.
Full rows look the same, so the class is safe to keep on every gallery row.
Where can I find free Bootstrap gallery templates?
FreeFrontend lists 20+ Bootstrap galleries with live demos, and MDB publishes gallery examples built on Bootstrap 5. Some MDB components need the paid MDB Pro package, so check before copying.
Confirm the Bootstrap version and license of any template first, since many results target Bootstrap 3 or 4.
How do I fill the gallery from JSON or a CMS?
Loop over the records and render one thumbnail and one carousel item per image, with the same index on both so the viewer opens the right slide.
Store the alt text as a field in the data, since each image needs its own description.
What to Build First in a Bootstrap Gallery
Start with the grid, thumbnail dimensions and alt text, then add the native modal and carousel viewer, and bring in a lightbox library last. Layout markup is expensive to change later, while the viewer swaps out cheaply.
- Grid with ratio boxes, width and height, alt text
- Modal with carousel and sync script
- Lightbox library, only when zoom or video appears
That order puts the markup first because filtering, masonry and any new viewer all read from it.
The trade-off is one rewrite. When zoom or video arrives, the viewer block (one modal and one script) gets replaced, while the grid and thumbnails stay untouched.
This sequence assumes Bootstrap 5.3 (checked October 2026) and changes once Bootstrap announces an end-of-life date for version 5.