Key Takeaways
- Bootstrap 5’s carousel uses
data-bs-*attributes and no jQuery dependency, simplifying custom JavaScript integration. - Custom controls require replacing or augmenting the default
.carousel-control-prevand.carousel-control-nextelements with your own styled triggers. - Thumbnail-based indicators are achievable with standard Bootstrap 5 indicator markup plus a small CSS addition.
- The
CarouselJavaScript class exposesprev(),next(), andto(index)methods for programmatic control from any element on the page. - Autoplay carousels must include
aria-liveregions and a pause-on-hover behaviour to satisfy WCAG 2.1 AA. - Core Web Vitals are affected by carousel images: lazy-load all slides except the first, and supply explicit width and height attributes.
Bootstrap 5 Carousel Foundations
Bootstrap 5 ships its carousel as a pure CSS plus Vanilla JS component. The controlling class is Carousel, exposed on window.bootstrap.Carousel when you import the full bundle. There is no jQuery dependency, unlike Bootstrap 3 and 4. The component listens for slide.bs.carousel and slid.bs.carousel events on the root element. Those events are the hook you will use for thumbnail sync later.
A minimal working carousel looks like this:
<div id="heroCarousel" class="carousel slide" data-bs-ride="carousel" data-bs-interval="5000">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="slide-1.jpg" class="d-block w-100" width="1200" height="600" alt="Slide 1">
</div>
<div class="carousel-item">
<img src="slide-2.jpg" class="d-block w-100" width="1200" height="600" alt="Slide 2">
</div>
<div class="carousel-item">
<img src="slide-3.jpg" class="d-block w-100" width="1200" height="600" alt="Slide 3">
</div>
</div>
</div>Supply explicit width and height on every <img> so the browser can reserve layout space before the image loads. Missing these attributes is one of the most common causes of Cumulative Layout Shift in carousels. For a deeper look at image optimisation across Bootstrap projects, see how to optimise Bootstrap 5 images for Core Web Vitals.

Replacing the Default Arrow Controls
The default .carousel-control-prev and .carousel-control-next elements render semi-transparent chevron icons positioned over the slide edges. To replace them: remove the default elements and wire up any clickable element using the Bootstrap Carousel JavaScript API.
<!-- Custom control buttons placed OUTSIDE the carousel div -->
<div class="carousel-custom-controls d-flex align-items-center gap-3 mt-3">
<button id="btnPrev" class="btn btn-outline-dark rounded-circle" aria-label="Previous slide">
<i class="bi bi-arrow-left"></i>
</button>
<span id="slideCounter" class="small fw-semibold">1 / 3</span>
<button id="btnNext" class="btn btn-outline-dark rounded-circle" aria-label="Next slide">
<i class="bi bi-arrow-right"></i>
</button>
</div><script>
const carouselEl = document.getElementById('heroCarousel');
const carousel = new bootstrap.Carousel(carouselEl, { ride: false });
document.getElementById('btnPrev').addEventListener('click', () => carousel.prev());
document.getElementById('btnNext').addEventListener('click', () => carousel.next());
// Update slide counter on every transition
carouselEl.addEventListener('slid.bs.carousel', (e) => {
const total = carouselEl.querySelectorAll('.carousel-item').length;
document.getElementById('slideCounter').textContent =
${e.to + 1} / ${total};
});
</script>Placing the controls outside the carousel wrapper is intentional. It lets you position them freely with Flexbox or CSS Grid without fighting Bootstrap’s absolute-positioned defaults. It also makes styling straightforward: swap btn-outline-dark for any colour variant, or replace the Bootstrap Icons with an SVG sprite.
Building a Thumbnail Indicator Strip
Dot indicators communicate “there are more slides” but not “what those slides contain.” A thumbnail strip communicates both. Bootstrap 5’s .carousel-indicators list accepts any child element, not just <button>, so you can embed small images directly.
<ol class="carousel-indicators carousel-thumb-indicators">
<li data-bs-target="#heroCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1">
<img src="thumb-1.jpg" width="80" height="50" alt="Thumbnail 1">
</li>
<li data-bs-target="#heroCarousel" data-bs-slide-to="1" aria-label="Slide 2">
<img src="thumb-2.jpg" width="80" height="50" alt="Thumbnail 2">
</li>
<li data-bs-target="#heroCarousel" data-bs-slide-to="2" aria-label="Slide 3">
<img src="thumb-3.jpg" width="80" height="50" alt="Thumbnail 3">
</li>
</ol><style>
.carousel-thumb-indicators {
position: static; / take it out of the overlay position /
margin: 0.75rem 0 0;
gap: 0.5rem;
justify-content: center;
}
.carousel-thumb-indicators li {
width: 80px;
height: 50px;
opacity: 0.5;
border: 2px solid transparent;
border-radius: 4px;
overflow: hidden;
transition: opacity 0.2s ease, border-color 0.2s ease;
cursor: pointer;
text-indent: 0;
background: none;
padding: 0;
margin: 0;
}
.carousel-thumb-indicators li.active,
.carousel-thumb-indicators li:hover {
opacity: 1;
border-color: var(--bs-primary);
}
.carousel-thumb-indicators li img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
</style>The critical declaration is position: static on the indicator list. By default, .carousel-indicators is absolutely positioned over the carousel. Removing that keeps your thumbnail strip in normal document flow below the slides, giving it proper spacing and no z-index conflicts.

Custom Transition Effects
Bootstrap 5’s carousel supports a fade transition out of the box via the .carousel-fade modifier class. For a crossfade instead of a slide, add the class to the root element:
<div id="heroCarousel" class="carousel slide carousel-fade" data-bs-ride="carousel">For anything more elaborate, such as a scale or blur transition, you override the CSS applied during the .active and outgoing states. Bootstrap 5 adds .carousel-item-next, .carousel-item-prev, and .carousel-item-start or .carousel-item-end classes during animation. You can hook custom keyframes onto these:
<style>
/ Scale-in effect on the incoming slide /
#heroCarousel .carousel-item {
transition: transform 0.6s ease, opacity 0.6s ease;
transform: scale(0.97);
opacity: 0;
}
#heroCarousel .carousel-item.active {
transform: scale(1);
opacity: 1;
}
</style>Keep transition durations at or below 600 milliseconds. Users with the prefers-reduced-motion media query set expect animations to be suppressed. Bootstrap 5 already includes a reduced-motion override in its stylesheet, but verify your custom transitions honour it by wrapping them in the appropriate media query.
If you are working inside the Canvas HTML Template, you can use the built-in CSS custom property --cnvs-themecolor for the active thumbnail border and any accent colours on control buttons, keeping your carousel in sync with the global theme without touching multiple files. This approach to theming is explored in detail in our post on SCSS vs CSS variables for theming Bootstrap 5.
Programmatic Autoplay and Pause Controls
Setting data-bs-ride="carousel" starts autoplay immediately on page load. For usability and WCAG 2.1 AA compliance, provide an explicit pause toggle. Bootstrap 5 handles hover pause automatically unless you set data-bs-pause="false", so the main addition is a play/pause button:
<button id="btnPlayPause" class="btn btn-sm btn-secondary" aria-pressed="false" aria-label="Pause carousel">
Pause
</button>
<script>
let isPlaying = true;
const playPauseBtn = document.getElementById('btnPlayPause');
playPauseBtn.addEventListener('click', () => {
if (isPlaying) {
carousel.pause();
playPauseBtn.textContent = 'Play';
playPauseBtn.setAttribute('aria-label', 'Play carousel');
playPauseBtn.setAttribute('aria-pressed', 'true');
} else {
carousel.cycle();
playPauseBtn.textContent = 'Pause';
playPauseBtn.setAttribute('aria-label', 'Pause carousel');
playPauseBtn.setAttribute('aria-pressed', 'false');
}
isPlaying = !isPlaying;
});
</script>Toggling aria-pressed is not optional. Without it, screen reader users have no reliable way to know whether the carousel is running or paused.
When Not to Use a Carousel
Carousels are frequently misused. Research consistently shows that slides beyond the first receive very low click rates, particularly on desktop where users have learned to ignore anything that resembles a banner. Consider these alternatives before committing to a carousel:
- Static hero with a single high-quality image: faster to render, simpler to maintain, no layout shift risk.
- CSS Grid image gallery: all images visible simultaneously, no interaction required. See 9 Bootstrap 5 gallery layouts for photographers and agencies for practical patterns.
- Product image zoom component: more appropriate for ecommerce product detail pages than a rotating carousel.
A carousel earns its place when you have three to five promotional items of equal priority (a car dealership homepage, a real estate listing, a hotel landing page) and need to surface all of them without extending page length significantly. Auto-advancing carousels with more than five slides almost always perform worse than a static layout.
Accessibility Checklist for Bootstrap 5 Carousels
Bootstrap 5’s carousel markup includes several ARIA attributes by default, but a custom implementation can lose them. Before shipping, verify the following:
- The carousel root has
role="region"andaria-label="Image carousel"or a descriptive label. - Every
.carousel-itemhasrole="group"andaria-label="Slide X of Y". - Custom prev/next buttons have meaningful
aria-labelvalues, not just icon content. - Keyboard users can operate the carousel: Tab to reach controls, Enter or Space to activate them.
- A live region (
aria-live="polite") announces the current slide to screen reader users. - Autoplay is off by default or paused immediately on keyboard focus entering the carousel.
Failing these checks is one of the common Bootstrap 5 mistakes covered in 8 common Bootstrap 5 mistakes and how to fix them. Accessibility audits from clients or procurement teams will flag an inaccessible carousel before any visual issue.
Frequently Asked Questions
Yes. Bootstrap 5 dropped the jQuery dependency entirely. The Carousel class is available on window.bootstrap.Carousel when you include the Bootstrap bundle script. Instantiate it with new bootstrap.Carousel(element, options) and call methods like next(), prev(), and to(index) directly in Vanilla JS.
Set data-bs-ride="false" on the carousel element, or instantiate it with new bootstrap.Carousel(el, { ride: false }). If you need autoplay that the user can toggle, use the carousel.pause() and carousel.cycle() methods as shown in the play/pause example above.
Instantiate the Carousel object in JavaScript and attach click event listeners to any element on the page. Call carousel.prev() or carousel.next() inside those listeners. The default data-bs-target attribute approach requires the trigger to reference the carousel’s ID, but the JS API has no such restriction.
slide.bs.carousel fires immediately when a slide transition begins, before any animation runs. slid.bs.carousel fires after the transition completes. Both events include from and to properties indicating the slide indexes. Use slid.bs.carousel for updating a counter or thumbnail indicator: you want the UI to reflect the completed state, not the in-progress one.
Three to five slides is generally accepted as the practical upper limit. Beyond five, users are very unlikely to interact with later slides, particularly on desktop. If you have more than five items to display, consider a grid layout, a masonry gallery, or a paginated component instead. Auto-advancing carousels with more than five slides compound the problem by reducing the time each item is visible.
Looking for a production-ready Bootstrap 5 HTML template? Browse Canvas Template demos and find the perfect starting point for your next project.
If you’re building with the Canvas HTML Template and want to ship production-ready Bootstrap 5 layouts faster, try Canvas Builder free — the visual builder that exports clean Canvas-ready markup in minutes.
Skip the setup and build it free
Spin up a complete Bootstrap 5 site, blog included, with Canvas Builder. No coding, no cost.
Canvas Team
Tutorials and tips for building beautiful Bootstrap 5 websites with the Canvas HTML Template and Canvas Builder.
More from the Canvas Blog