Carousel
A carousel cycles through a set of slides — usually images — one at a time, with optional controls, indicator dots, and autoplay. We'll build TrailMap's "featured trail photos" carousel that appears near the top of every trail-detail page.
The Core Structure: carousel, carousel-inner, carousel-item
Every carousel needs a unique id (used by the controls and indicators to
target it), an .carousel-inner wrapper, and one .carousel-item
per slide — exactly one of which must start with the extra .active class:
<div id="trailPhotos" class="carousel slide">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="ridgeline-1.jpg" class="d-block w-100" alt="Ridgeline Loop overlook">
</div>
<div class="carousel-item">
<img src="ridgeline-2.jpg" class="d-block w-100" alt="Ridgeline Loop switchbacks">
</div>
</div>
</div>
.active on exactly one .carousel-item is the single most common carousel bug — with none active, nothing renders at all; with two active, they stack on top of each other.Prev/Next Controls
The arrow controls each target the carousel by data-bs-target and declare
their direction with data-bs-slide:
<button class="carousel-control-prev" type="button"
data-bs-target="#trailPhotos" 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="#trailPhotos" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
Indicators and Autoplay
.carousel-indicators renders the row of small dots at the bottom, one
<button> per slide, each with data-bs-slide-to set to
that slide's zero-based index:
<div class="carousel-indicators">
<button type="button" data-bs-target="#trailPhotos"
data-bs-slide-to="0" class="active" aria-current="true"></button>
<button type="button" data-bs-target="#trailPhotos"
data-bs-slide-to="1"></button>
</div>
To make the carousel cycle automatically, add data-bs-ride="carousel" on the
outer .carousel element itself — it starts autoplaying as soon as the page
loads (use data-bs-ride="true" instead if you only want it to start once the
user has interacted with it once):
<div id="trailPhotos" class="carousel slide" data-bs-ride="carousel"> ... </div>
Captions
.carousel-caption inside a .carousel-item overlays a title and
short text on top of the slide, typically bottom-aligned and centered:
<div class="carousel-item active">
<img src="ridgeline-1.jpg" class="d-block w-100" alt="Ridgeline Loop overlook">
<div class="carousel-caption d-none d-md-block">
<h5>Summit Overlook</h5>
<p>Mile 3.4, best light around sunrise.</p>
</div>
</div>
Worked Example: TrailMap Featured Trail Photos
Putting every piece together for the Ridgeline Loop trail-detail page — three slides, controls, indicators, autoplay, and captions:
<div id="trailPhotos" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-indicators">
<button type="button" data-bs-target="#trailPhotos" data-bs-slide-to="0" class="active"></button>
<button type="button" data-bs-target="#trailPhotos" data-bs-slide-to="1"></button>
<button type="button" data-bs-target="#trailPhotos" data-bs-slide-to="2"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
<img src="ridgeline-1.jpg" class="d-block w-100" alt="Summit overlook">
<div class="carousel-caption d-none d-md-block">
<h5>Summit Overlook</h5>
<p>Mile 3.4, best light around sunrise.</p>
</div>
</div>
<div class="carousel-item">
<img src="ridgeline-2.jpg" class="d-block w-100" alt="Switchback climb">
<div class="carousel-caption d-none d-md-block">
<h5>The Switchbacks</h5>
<p>Mile 1.8–2.6, the steepest stretch.</p>
</div>
</div>
<div class="carousel-item">
<img src="ridgeline-3.jpg" class="d-block w-100" alt="Trailhead sign">
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#trailPhotos" 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="#trailPhotos" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
Three .carousel-item elements, three matching indicator dots
(data-bs-slide-to="0" through "2"), autoplay from
data-bs-ride="carousel", and captions on the first two slides only — nothing
here needed a line of custom JavaScript either.