Carousel
A carousel is a slideshow: one slide is visible at a time, and the others slide in when you click an arrow or after a few seconds. You see them on shop home pages for offers, on college sites for campus photos, and in apps for onboarding screens. Bootstrap's carousel plugin handles the sliding, the controls, touch swiping and keyboard arrows.
In this lesson you will build a carousel with controls, indicators and captions, adjust its timing and effects, control it from JavaScript, and learn when a carousel is the wrong choice.
Carousel structure
| Element | Purpose |
|---|---|
carousel slide with an id | The outer box. slide adds the sliding animation |
carousel-inner | Holds the slides and hides the ones not shown |
carousel-item | One slide. Exactly one must also have active |
carousel-control-prev / -next | Previous and next buttons |
carousel-indicators | Small buttons, one per slide |
carousel-caption | Text placed over a slide |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Carousel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-3" style="max-width: 720px">
<div id="campusCarousel" class="carousel slide">
<div class="carousel-indicators">
<button type="button" data-bs-target="#campusCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1: Main building"></button>
<button type="button" data-bs-target="#campusCarousel" data-bs-slide-to="1" aria-label="Slide 2: Library"></button>
<button type="button" data-bs-target="#campusCarousel" data-bs-slide-to="2" aria-label="Slide 3: Sports ground"></button>
</div>
<div class="carousel-inner rounded">
<div class="carousel-item active">
<img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" class="d-block w-100" alt="Main building of the Pune campus at sunrise" width="800" height="500">
<div class="carousel-caption d-none d-md-block">
<h2 class="h5">Main building</h2>
<p>Classrooms, labs and the admission office.</p>
</div>
</div>
<div class="carousel-item">
<img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" class="d-block w-100" alt="Library reading hall" width="800" height="500">
<div class="carousel-caption d-none d-md-block">
<h2 class="h5">Library</h2>
<p>Open 8 am to 10 pm, with 40,000 books.</p>
</div>
</div>
<div class="carousel-item">
<img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" class="d-block w-100" alt="Cricket and football ground" width="800" height="500">
<div class="carousel-caption d-none d-md-block">
<h2 class="h5">Sports ground</h2>
<p>Cricket nets and a full-size football field.</p>
</div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#campusCarousel" 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="#campusCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
<p class="mt-2 small text-body-secondary" id="slideInfo">Slide 1 of 3</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById("campusCarousel").addEventListener("slid.bs.carousel", (event) => {
document.getElementById("slideInfo").textContent = `Slide ${event.to + 1} of 3`;
});
</script>
</body>
</html>How the parts connect:
- Every control and indicator has
data-bs-target="#campusCarousel", the carousel's id.data-bs-slide="prev"or"next"moves one step;data-bs-slide-to="1"jumps to a slide by index (counting from 0). - The arrow icons are images, so they have
aria-hidden="true", and avisually-hiddenspan gives each button a name. The indicators havearia-labels for the same reason. d-block w-100on the images removes the gap below inline images and makes every slide fill the same width. Use images with the same shape, or the carousel's height jumps between slides.carousel-caption d-none d-md-blockhides captions on phones, where they would cover most of the picture.- The
slid.bs.carouselevent fires after each slide change. Itsevent.toandevent.fromare slide indexes, andevent.directionis"left"or"right".
This carousel does not move by itself. That is deliberate, as the next section explains.
Autoplay and timing
To start sliding automatically when the page loads, add data-bs-ride="carousel". The options, written as data-bs-* attributes on the carousel or passed in JavaScript:
| Option | Default | Meaning |
|---|---|---|
ride | false | "carousel": autoplay on load. true: autoplay after the user first uses a control |
interval | 5000 | Milliseconds between slides; also settable per slide with data-bs-interval on a carousel-item |
pause | "hover" | Pauses while the mouse is over it; false never pauses |
wrap | true | Loops from the last slide back to the first |
keyboard | true | Left and right arrow keys move the slides |
touch | true | Swiping works on touch screens |
<div id="offers" class="carousel slide carousel-fade" data-bs-ride="carousel" data-bs-interval="4000">
<div class="carousel-inner">
<div class="carousel-item active" data-bs-interval="8000">...first offer stays 8 seconds...</div>
<div class="carousel-item">...</div>
</div>
</div>carousel-fade cross-fades the slides instead of sliding them.
The JavaScript API
const el = document.getElementById("campusCarousel");
const carousel = bootstrap.Carousel.getOrCreateInstance(el, { interval: 3000 });
carousel.next(); // next slide
carousel.prev(); // previous slide
carousel.to(2); // jump to the third slide
carousel.cycle(); // start autoplay
carousel.pause(); // stop autoplayHere is a carousel with a working pause/play button, which is how an autoplaying carousel should behave:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Carousel with pause</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-3" style="max-width: 560px">
<div id="quotes" class="carousel slide border rounded p-4 text-center" data-bs-ride="carousel" data-bs-interval="3000">
<div class="carousel-inner">
<div class="carousel-item active"><p class="lead mb-0">"Best samosas in Lucknow." Rohan</p></div>
<div class="carousel-item"><p class="lead mb-0">"Delivery was hot and on time." Ananya</p></div>
<div class="carousel-item"><p class="lead mb-0">"The kulfi is worth the trip." Kabir</p></div>
</div>
</div>
<div class="d-flex gap-2 justify-content-center mt-2">
<button type="button" class="btn btn-sm btn-outline-secondary" id="prevBtn">Previous</button>
<button type="button" class="btn btn-sm btn-secondary" id="pauseBtn" aria-pressed="false">Pause</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="nextBtn">Next</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
const quotes = bootstrap.Carousel.getOrCreateInstance(document.getElementById("quotes"));
const pauseBtn = document.getElementById("pauseBtn");
let paused = false;
pauseBtn.addEventListener("click", () => {
paused = !paused;
if (paused) {
quotes.pause();
} else {
quotes.cycle();
}
pauseBtn.textContent = paused ? "Play" : "Pause";
pauseBtn.setAttribute("aria-pressed", String(paused));
});
document.getElementById("prevBtn").addEventListener("click", () => quotes.prev());
document.getElementById("nextBtn").addEventListener("click", () => quotes.next());
</script>
</body>
</html>This testimonial carousel uses text slides, which shows that a slide can contain any HTML, not only images. When a carousel has light slides, add data-bs-theme="dark" to it: the arrows, indicators and captions turn dark so they stay visible.
Common mistakes
- No slide with
active. The carousel shows nothing at all. - Images of different sizes. The height jumps with every slide. Crop images to the same ratio.
- Controls with the wrong
data-bs-target. The arrows do nothing. It must be the carousel's id. - Autoplay with no way to stop it. Provide a pause control, or do not autoplay.
Key points
- A carousel is
carousel slide>carousel-inner>carousel-item(oneactive), with optional controls, indicators and captions. - Controls and indicators point at the carousel with
data-bs-targetand usedata-bs-slideordata-bs-slide-to. data-bs-ride="carousel"autoplays;data-bs-interval,pause,wrap,keyboardandtouchtune behaviour.bootstrap.Carousel.getOrCreateInstance(el)providesnext(),prev(),to(),cycle()andpause(), andslid.bs.carouselreports changes.- Autoplaying carousels need a pause control; often a static layout is better.
Exercise
Build a carousel of three festival offers for a sweet shop in Jaipur (Diwali, Holi and Raksha Bandhan), using landscape.svg for each slide with a caption. Use the fade effect, add indicators and previous/next controls, and do not autoplay. Below it, add three small buttons labelled with the festival names that jump straight to the matching slide using the JavaScript API.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Festival offers</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-3" style="max-width: 720px">
<div id="offers" class="carousel slide carousel-fade">
<div class="carousel-indicators">
<button type="button" data-bs-target="#offers" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Diwali offer"></button>
<button type="button" data-bs-target="#offers" data-bs-slide-to="1" aria-label="Holi offer"></button>
<button type="button" data-bs-target="#offers" data-bs-slide-to="2" aria-label="Raksha Bandhan offer"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
<img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" class="d-block w-100" alt="Diwali sweets box" width="800" height="500">
<div class="carousel-caption"><h2 class="h5">Diwali: 20% off dry-fruit boxes</h2></div>
</div>
<div class="carousel-item">
<img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" class="d-block w-100" alt="Holi gujiya platter" width="800" height="500">
<div class="carousel-caption"><h2 class="h5">Holi: gujiya at ₹480 per kg</h2></div>
</div>
<div class="carousel-item">
<img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" class="d-block w-100" alt="Rakhi gift hamper" width="800" height="500">
<div class="carousel-caption"><h2 class="h5">Raksha Bandhan: free rakhi with every hamper</h2></div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#offers" 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="#offers" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span><span class="visually-hidden">Next</span>
</button>
</div>
<div class="d-flex gap-2 mt-2">
<button type="button" class="btn btn-sm btn-outline-primary" data-go="0">Diwali</button>
<button type="button" class="btn btn-sm btn-outline-primary" data-go="1">Holi</button>
<button type="button" class="btn btn-sm btn-outline-primary" data-go="2">Raksha Bandhan</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
const offers = bootstrap.Carousel.getOrCreateInstance(document.getElementById("offers"));
document.querySelectorAll("[data-go]").forEach((btn) => {
btn.addEventListener("click", () => offers.to(Number(btn.dataset.go)));
});
</script>
</body>
</html>There is no data-bs-ride, so the carousel waits for the user. carousel-fade changes the transition, and offers.to() jumps to the slide index stored in each button's data-go attribute.
Quick Quiz
1. What happens if no carousel-item has the active class?
Bootstrap shows only the active slide, so one item must start with active.
2. Which attribute makes a carousel start sliding on page load?
ride set to carousel starts cycling immediately; interval only sets the timing.
3. How do you jump to the third slide from JavaScript?
Slide indexes start at 0, so the third slide is index 2.
4. Why should an autoplaying carousel have a pause button?
Moving content can make a page unusable for some users, so they must be able to stop it.
Learn Bootstrap with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
