What is the Bootstrap carousel and what does it need to run?
The Bootstrap framework holds a 74.8% share among sites with a known CSS framework, according to W3Techs (October 2026).
That reach is why carousel snippets are everywhere. It is also why outdated ones keep ranking, and several top tutorials still show Bootstrap 3.4.1 and 4.x markup.
The component itself is a slideshow for images, text, or custom markup. People mostly use it for hero banners and product galleries, plus the occasional testimonial rotator.
The previous and next controls and the indicators are built in, but neither is required.
On Bootstrap 5.3 you load the CSS file plus either bootstrap.js or bootstrap.min.js, never both. There is no jQuery. A CDN such as jsDelivr works fine, or you can install the bootstrap package with npm. Bootstrap 4.6 still initializes through jQuery, and Bootstrap 4 has reached end of life (Bootstrap 4.6 docs).
Setup is manual with new bootstrap.Carousel() unless the carousel autoplays. One marked data-bs-ride="carousel" starts by itself on page load.
Popper isn’t involved. It belongs to tooltips and popovers, along with dropdowns (Bootstrap docs).
The sliding itself is CSS. Transforms do the motion, and JavaScript handles timing, controls, and events.
The limits are short. Nested carousels aren’t supported and only one slide shows at a time. Slide dimensions aren’t normalized either, so that part is on you. The Bootstrap docs also warn that carousels in general can cause usability and accessibility problems.
How to build a Bootstrap carousel step by step
A working carousel is a .carousel wrapper with a unique id, a .carousel-inner container, slides with .active on one of them, and the Bootstrap JavaScript file. Controls, indicators, and captions are extras.
- Load the Bootstrap CSS in the head and the JavaScript file before the closing body tag.
- Create a div with the classes
carousel and slide and give it a unique id. - Add a
.carousel-inner div inside it. - Add one
.carousel-item per slide and put .active on the first. - Place an image with
d-block w-100 and real alt text in each slide. - Add previous/next buttons and indicators that point to the carousel id.
- Add
data-bs-ride="carousel" only if the carousel should autoplay on load.
Base markup
This is the smallest carousel that renders on Bootstrap 5.3.
<div id="heroCarousel" class="carousel slide">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="slide-1.jpg" class="d-block w-100" alt="Describe slide one">
</div>
<div class="carousel-item">
<img src="slide-2.jpg" class="d-block w-100" alt="Describe slide two">
</div>
</div>
</div>
The docs state it plainly: without .active on one slide, the carousel is not visible.
The d-block and w-100 classes stop the browser’s default inline image alignment. The whole component is plain HTML plus data attributes, so there is nothing to compile.
Previous and next controls
Each control is a button with the class carousel-control-prev or carousel-control-next. The docs recommend buttons, though anchors with role="button" work too.
Point it at the carousel with data-bs-target, which holds the carousel id, such as #heroCarousel (links use href instead). data-bs-slide takes prev or next. Screen readers need a visually-hidden span reading Previous or Next, and the icon span gets aria-hidden="true".
If the target and the id do not match, the buttons do nothing. With several carousels on one page, every id has to be unique, which is easy to forget when you duplicate a block.
Indicators
Indicators let users jump straight to a slide. Wrap them in a div with the class carousel-indicators and use one button per slide, each with data-bs-slide-to set to the slide index, starting at 0. The first button gets active and aria-current="true", and every button gets an aria-label such as “Slide 1”.
Count the buttons against the slides. A third button on a two-slide carousel points nowhere.
Captions
A caption is a div with the class carousel-caption, placed inside the .carousel-item it belongs to.
Bootstrap’s own example hides captions on small screens with d-none and brings them back from the medium breakpoint with d-md-block.
Keep a caption to a heading and one line. Anything longer covers the picture on a phone.
How do you stop, start, or change carousel autoplay?
The ride option controls autoplay, and in markup it is data-bs-ride. Use carousel to start on page load, or true to start after the first manual slide change. The pause between slides is the interval option, 5000 ms unless you change it.
| Option | Default | Effect |
|---|
| ride | false | “carousel” autoplays on load, true autoplays after the first manual change |
| interval | 5000 | Milliseconds between automatic slide changes |
| pause | “hover” | Stops on mouseenter, resumes on mouseleave |
| wrap | true | Loops continuously, or stops at the first and last slide when false |
Stop autoplay
In markup, remove data-bs-ride from the .carousel element, since the default is false. In a script, pause() stops a running carousel and cycle() starts it again.
Versions disagree on one detail. The Bootstrap 4.6 docs name interval set to false as an off switch, but the 5.3 options table lists interval as a number only, so on 5.3 remove ride instead.
Change the speed
The default delay is 5000 ms, or 5 seconds.
Set a carousel-wide value with data-bs-interval on the .carousel element, or pass interval: 2000 to the constructor. To override one slide, put data-bs-interval on its .carousel-item.
Bootstrap’s demo gives the first slide 10000 and the second 2000, and the third keeps the carousel-wide value.
Pause, touch, and keyboard behavior
Plenty happens in the background here, and most of it is on by default. With pause set to “hover”, touch devices pause on touchend for two intervals before resuming. Browsers with the Page Visibility API stop the cycle when the tab is inactive or the window is minimized. touch: false turns off swiping, and keyboard: false stops the carousel from reacting to key presses.
Autoplay is a usability decision as much as a setting. The Bootstrap docs recommend avoiding autoplaying carousels, and adding an explicit pause control when you keep one. I’d leave it off unless there’s a reason.
How do you change the transition and style of a carousel?
A crossfade comes from .carousel-fade, and dark controls and captions come from data-bs-theme="dark". Speed is $carousel-transition-duration, 0.6 seconds by default. Slide is the default transition.
Switch to a crossfade
By default slides move horizontally. Add carousel-fade to the .carousel and they blend in place instead.
Text-only slides need a solid background for the crossfade to look right. Bootstrap suggests .bg-body on each .carousel-item, or custom CSS.
Change the slide speed
Speed is a Sass variable, not a data attribute.
$carousel-transition-duration defaults to .6s as of Bootstrap 5.3.8. Override it before compiling, or write custom CSS on .carousel-item if you use the compiled file.
Stack a second transition and order matters: define the transform first, as in transition: transform 2s ease, opacity .5s ease-out (Bootstrap docs).
If nothing animates during testing, check the operating system’s reduced motion setting. Bootstrap disables the sliding animation when prefers-reduced-motion is active.
Dark variant and Sass variables
.carousel-dark was deprecated in v5.3.0. Set data-bs-theme="dark" on the root element, a wrapper, or the carousel itself. The $carousel-dark-* variables followed in v5.3.4.
Sizing has its own variables. $carousel-control-width is 15%, $carousel-indicator-width is 30px, $carousel-indicator-height is 3px, and $carousel-caption-width is 70%.
Caption text sits on photos, so check color contrast against the brightest part of each image. Bootstrap’s accessibility docs cite the WCAG 2.2 text ratio of 4.5:1 and warn that some default palette combinations can fall below it.
How do you size carousel images and keep them fast?
Bootstrap does not normalize slide dimensions, so sizing is left to you. Crop every image to one aspect ratio, give each d-block and w-100, and keep the first slide out of any lazy loading.
Keep slide heights equal
The simplest fix is to export all slides at one ratio, 16:9 for example. If that isn’t possible, set a fixed height on the images in CSS and add Bootstrap’s object-fit-cover utility.
Because w-100 stretches the image to its container, it suits a responsive design. It does not shrink the file, so serve smaller versions to small screens with srcset and sizes.
Also add width and height attributes, or a CSS aspect-ratio, so the browser reserves space before the image loads. On mobile, 62% of pages leave at least one image without explicit dimensions (HTTP Archive Web Almanac 2025).
Load the first slide first
About 16% to 17% of pages lazy-load their Largest Contentful Paint image, a share that has held steady since 2024 (HTTP Archive Web Almanac 2025).
A carousel at the top of a page makes that slip easy. Slide one becomes the page’s hero image, and a blanket loading="lazy" on every slide delays it.
Chrome for Developers (October 2025) gives the general rule: keep the LCP image discoverable in the HTML, add fetchpriority="high", and avoid loading="lazy" on it.
For a carousel, that means slide one gets fetchpriority="high" and no loading="lazy". Later slides can be lazy. Slide two is the exception when the interval is short, since a 2000 ms delay leaves little time to load, so keep it eager too.
Bootstrap’s docs cover neither loading attribute, so this rule comes from combining Chrome’s LCP guidance with the carousel showing one slide at a time.
How do you control the carousel with JavaScript?
Create an instance with new bootstrap.Carousel(element, options), then call next(), prev(), or to(index) on it. The events slide.bs.carousel and slid.bs.carousel report what the carousel is doing.
Create and retrieve an instance
The constructor takes a DOM element or a CSS selector, plus an optional options object such as { interval: 2000, touch: false }.
Everything in this section is plain JavaScript on Bootstrap 5.3. getInstance(element) returns the existing instance, or null if there isn’t one. getOrCreateInstance(element, options) returns the existing instance or builds one. dispose() removes the stored data from the element. bootstrap.Carousel.Default holds the default options, and a change there applies to every carousel created afterward.
Prefer getOrCreateInstance in shared scripts. For a carousel Bootstrap already initialized on load, it hands back that instance instead of building a second one.
Methods
| Method | What it does |
|---|
| next | Moves to the next slide |
| prev | Moves to the previous slide |
| to(index) | Jumps to a slide by zero-based index |
| nextWhenVisible | Moves on only when the page, the carousel, and its parent are visible |
pause() and cycle() belong to the same set.
Asynchronous calls
Every method returns before its transition ends.
The docs add that a method call on a transitioning carousel is ignored, and their example chains two to() calls by running the second one from a slid.bs.carousel listener. In the 5.3.8 source, next() and prev() are dropped mid-transition, while to() waits for the current slide to finish and then runs.
const el = document.querySelector('#heroCarousel')
const carousel = bootstrap.Carousel.getOrCreateInstance(el)
el.addEventListener('slid.bs.carousel', () => carousel.to(2), { once: true })
carousel.to(1)
The { once: true } flag stops the listener from firing after every later slide.
Events
Both events fire on the .carousel element itself. slide.bs.carousel fires the moment a slide change starts, and slid.bs.carousel fires when it finishes.
Each one carries a direction of “left” or “right” and a relatedTarget, the slide element moving into place. The from and to properties hold the indexes of the current slide and the next one.
Together with the methods, these events make up the carousel’s programmatic API. Use them to sync a slide counter or prepare content for the incoming slide.
One catch with jQuery. If it is on the page and the body has no data-bs-no-jquery attribute, Bootstrap emits events through jQuery’s event system, so listen with .on() and not addEventListener (Bootstrap 5.3 docs).
Is the Bootstrap carousel accessible?
Not from the default markup. An autoplaying Bootstrap carousel lacks the rotation control, focus-based pausing, and container and slide semantics that the W3C carousel pattern calls for.
It can get there. Bootstrap’s accessibility page says WCAG 2.2 compliance depends on the author’s markup and scripting, and the carousel needs both.
What WCAG 2.2 requires
Success Criterion 2.2.2, Pause, Stop, Hide, sits at Level A (W3C Understanding document, updated August 2026). Auto-updating content that starts on its own and appears beside other content needs a way to pause it, stop it, hide it, or control how often it updates.
The Understanding document counts auto-advancing presentations as auto-updating content, and auto-updating content gets no five-second exception. A carousel with autoplay on is in scope however long its interval is.
Hover-pausing does not qualify as a mechanism. The document rejects an animation that stops only while the user has focus on it, and hover-only pausing has the same weakness.
Level A is the entry point of web accessibility conformance, so autoplay without a control fails at the lowest level.
What the W3C carousel pattern requires
The ARIA Authoring Practices Guide lays out what an accessible autoplaying carousel looks like. Bootstrap’s 5.3 docs example covers none of it, as the table shows.
| Requirement | Bootstrap 5.3 docs example | What you add |
|---|
| Rotation control, first in the tab order | None built in | A button that calls pause() and cycle(), labeled like the APG example, “Stop automatic slide show” or “Start automatic slide show” |
| Stop when keyboard focus enters | Hover and touch pause only | A focusin listener that calls pause() |
| Carousel container semantics | No role or label | role=”region” (or role=”group”), aria-roledescription=”carousel”, aria-label |
| Slide semantics | None | role=”group”, aria-roledescription=”slide”, a label such as “3 of 10” |
| Live region on the slide wrapper (optional in the pattern) | None | aria-live “off” while rotating, “polite” when stopped |
Those roles and properties are ARIA. Bootstrap’s accessibility page expects authors to add further ARIA roles and attributes where a component needs them.
What Bootstrap’s own docs say
The Bootstrap 4.6 docs call carousels generally non-compliant with accessibility standards. The 5.3 docs soften that to carousels often causing usability and accessibility challenges, with a pointer to WCAG 2.2.2 and a recommendation to add a pause control.
The softer wording fits the facts. The stock markup falls short, and a carousel with the missing pieces added can comply.
The TYPO3 Bootstrap Package took that route, adding role="region" and aria-roledescription="carousel" to its carousel templates.
What changed between the Bootstrap 4 and Bootstrap 5 carousel?
Bootstrap 5 prefixed every carousel data attribute with data-bs-, dropped jQuery, and rebuilt the indicators from a list into buttons. Bootstrap 5 does not read the old attribute names, so a pasted v4 snippet loses autoplay and working controls.
| Item | Bootstrap 4.6 | Bootstrap 5.3 |
|---|
| Autoplay attribute | data-ride=”carousel” | data-bs-ride=”carousel” |
| Control attributes | data-target, data-slide, data-slide-to | data-bs-target, data-bs-slide, data-bs-slide-to |
| Option attributes | data-interval, data-touch | data-bs-interval, data-bs-touch |
| Indicator markup | ol with li elements | div with buttons, aria-label, aria-current |
| Script call | $(‘.carousel’).carousel() | new bootstrap.Carousel(‘#myCarousel’) |
Event names stay the same. So do cycle, pause, next, and prev, though v4 calls them through jQuery and v5 through an instance, and v4’s .carousel(number) became to(index).
Version 5.0.0 removed jQuery and util.js, so plugins run as plain JavaScript (the 4.6 docs say source builds need util.js). It also renamed the sr-only class to visually-hidden, which affects the Previous and Next text. Then 5.2.0 added an experimental data-bs-config attribute that accepts options as a JSON string.
Version 5.0.0 also swapped the chevron control icons for SVGs from Bootstrap Icons.
It dropped Internet Explorer 10 and 11 as well. If your audience still includes old browsers, test cross-browser compatibility before upgrading.
Build new carousels on 5.3, currently 5.3.8. Migrating an old one starts with renaming the attributes and rewriting the indicators.
Why is a Bootstrap carousel not working?
Beyond a missing .active slide or a mismatched id, the failures Bootstrap documents come from initialization, framework conflicts, and selectors. Check those first.
| Problem | Cause | Fix |
|---|
| Swipe does nothing until a control is clicked | No data-bs-ride, so touch listeners are not registered until a control or indicator is used | Create the instance on load with new bootstrap.Carousel(‘#id’) |
| Same carousel initialized twice | data-bs-ride=”carousel” plus the constructor on one element | Keep one route, since the docs say not to combine them |
| Carousel misbehaves inside React, Vue, or Angular | Bootstrap and the framework both mutate the same DOM element | Use React-Bootstrap, ng-bootstrap, or BootstrapVueNext (alpha in the 5.3 docs) instead of Bootstrap’s JavaScript |
| Nothing moves with JavaScript disabled | The plugins have no built-in fallback | Add a noscript notice or your own static fallback |
Ids deserve a second look too. Since 5.3.0, Bootstrap runs the id in data-bs-target and in selector strings through CSS.escape, so an id such as hero:main works there. Your own querySelector calls still need it escaped.
Start ids with a letter and skip colons and dots, and the problem never comes up.
When is the Bootstrap carousel the wrong choice?
Use it for a single-slide rotator on a page that already loads Bootstrap. Move to Swiper or Splide for multi-slide layouts, and drop rotation entirely when users must see the content.
Do users see what a carousel shows?
Nielsen Norman Group (January 2013) watched a user miss a prominent discount on Siemens’ U.K. appliance homepage because the panel auto-rotated every 5 seconds. The offer was visible 20% of the time.
Bootstrap’s default interval is the same 5000 ms. With autoplay on, five slides each get about 20% of screen time, and three slides get about 33%.
Click data from the University of Notre Dame web team (Erik Runyon, January to June 2013) points the other way. On ND.edu, 1.07% of homepage visitors clicked a feature, and position one took 89.1% of those clicks. The only autoplaying site in the set, an aggregated news site, saw 9.409% click a feature.
The two sources disagree on autoplay. NN/g’s study points against it, while Runyon’s autoplaying site drew the highest feature click rate of his five sites.
Runyon does not credit autoplay for that, and a news aggregator’s content differs from a university’s, so the data cannot settle the question. Both datasets are from 2013, and no newer first-party carousel click data was found.
When the content decides a purchase or a task, show it as static content. Keeping the carousel for items users can skip protects the user experience of everything else on the page.
Bootstrap carousel, Swiper, or Splide?
Two libraries cover what the built-in component does not. A hand-built JavaScript carousel is the other route, and the most work.
Bootstrap carousel
It ships with Bootstrap, so there is no extra download, and the basic case needs no script because data attributes do the configuring. Controls, indicators, captions, fade, and touch swipe are all included.
One slide shows at a time and nesting is out. The pause control and ARIA semantics are yours to add, and slide dimensions need manual sizing.
Swiper
Swiper shows several slides per view through slidesPerView, takes per-breakpoint configuration, has an A11y module that adds ARIA labels and roles, and comes with official React and Vue components. The price is a second library next to Bootstrap, with its own modules and stylesheet to import.
Splide
Splide’s README (v4.1.4) lists 29 kB, 12 kB gzipped, and no dependencies.
| Pros | Cons |
|---|
| Multiple slides, thumbnail slider, and nested sliders | Own markup (splide__track, splide__list, splide__slide) and API, so Bootstrap attributes do not carry over |
| Autoplay with a play-pause button, plus keyboard control and ARIA attributes | A separate stylesheet and script to load |
Bootstrap Carousel FAQ
How do you change the carousel arrow icons?
Override the $carousel-control-prev-icon-bg and $carousel-control-next-icon-bg Sass variables with your own SVG data URI before compiling.
On the compiled CSS, replace the background image on the two control icon spans instead.
Can a Bootstrap carousel show more than one item per slide?
Yes, through custom markup. Put a row of cards inside each carousel item and the carousel advances one group at a time.
Sliding single cards takes a library such as Swiper (slidesPerView) or Splide.
How do you add thumbnail navigation?
Style each indicator button as a small image. The buttons already jump to a slide through data-bs-slide-to, so only the look changes.
Splide ships a thumbnail slider if hand-styling is not worth it.
How do you make a full-screen carousel?
Add vh-100 and object-fit-cover to each slide image, next to w-100, so photos fill the viewport without stretching.
Keep the carousel outside any .container wrapper, or it stays at the container width.
Which package should I use for a Bootstrap carousel in React?
React-Bootstrap. Bootstrap’s docs list it as the React option and link a working Next.js demo in the twbs/examples repository.
Can you nest one carousel inside another?
No. Bootstrap’s docs state that nested carousels are not supported.
For a slider inside a slider, Splide’s feature list includes a nested slider.
What to Verify After Launching a Bootstrap Carousel
Check keyboard operation first, then reduced-motion behavior, then first-slide loading.
- Tab through controls and indicators, then stop autoplay without a mouse
- Toggle the operating system’s reduced motion setting
- Confirm slide one loads eagerly and later slides load lazily
The order follows the cost of failure. A keyboard dead end blocks a user outright, a motion setting changes what a user sees, and a slow first slide only delays it.
Each fix adds code the stock component lacks, and every Bootstrap upgrade means retesting it.
Position verified against Bootstrap 5.3.8 in October 2026. Re-check the data attribute names, the deprecated .carousel-dark class, and the Sass variables when a new major version ships.
A full web accessibility checklist extends these three checks to the rest of the page.