On this page
Carousel
A sliding gallery with Bootstrap’s carousel classes. The browser owns snapping, swipe, and (where supported) previous/next and indicators — no Carousel plugin.
| What runs it | JavaScript | |
|---|---|---|
| Bootstrap 5.3 | Carousel plugin (slide, swipe, indicators, keyboard) | 6.7 KB gzip |
| This repo | CSS scroll snap. Optional pe-carousel when ::scroll-button is missing. | 0 KB. Optional behavior is 1.4 KB gzip. |
When that API is missing,
this page also loads invoker-commands.js (0.8 KB gzip).
See Polyfills.
| Optional | <pe-carousel> · src/behaviors/carousel.js . Prev, next, and pip controls where ::scroll-button is missing. Inert where it is not. |
|---|---|
| Avoid |
|
::scroll-button
and
::scroll-marker
(Chrome 135+, not native in Firefox or Safari). Scroll snap and
swipe work without those pseudos. The first examples stay on that
native path.
<pe-carousel>
is a later option for browsers that lack
::scroll-button.
How it works
-
.carousel-inneris a horizontal scroller withscroll-snap-type: x mandatory. That replaces Bootstrap’stransform+ JavaScript. -
Every
.carousel-itemis in the row (flex: 0 0 100%)..activeis not required for the first slide to show. -
Do not add HTML
.carousel-control-prev/.carousel-control-nextor.carousel-indicators. Those need script. The browser generates equivalent controls from CSS. -
Put
data-labelon each item so::scroll-markerhas an accessible name (content: attr(data-label)). -
Without
::scroll-button, swipe, drag the scrollbar, or focus the scroller and use the keyboard. HTML buttons, pips, and mouse panning are the fallback, not this markup. -
scroll-behavior: smoothfollowsprefers-reduced-motion.
data-bs-ride), crossfade
(.carousel-fade), and wrap-around cycling are not
CSS. This demo does not implement them.
Slides
Swipe or use the generated previous/next buttons. Indicator pips jump to a slide. All three slides stay in the DOM — none are display: none.
<div class="carousel slide">
<div class="carousel-inner">
<div class="carousel-item" data-label="Slide 1">
<div class="d-flex w-100 align-items-center justify-content-center bg-secondary text-white" style="min-height: 16rem">Basic first slide</div>
</div>
<div class="carousel-item" data-label="Slide 2">
<div class="d-flex w-100 align-items-center justify-content-center bg-dark text-white" style="min-height: 16rem">Basic second slide</div>
</div>
<div class="carousel-item" data-label="Slide 3">
<div class="d-flex w-100 align-items-center justify-content-center bg-secondary text-white" style="min-height: 16rem">Basic third slide</div>
</div>
</div>
</div> Captions
Add .carousel-caption inside any .carousel-item. Hide them on small viewports with .d-none .d-md-block as in Bootstrap’s docs.
<div class="carousel slide">
<div class="carousel-inner">
<div class="carousel-item" data-label="First slide">
<div class="d-flex w-100 align-items-center justify-content-center bg-secondary text-white" style="min-height: 16rem">First slide</div>
<div class="carousel-caption d-none d-md-block">
<h5>First slide label</h5>
<p>Some representative placeholder content for the first slide.</p>
</div>
</div>
<div class="carousel-item" data-label="Second slide">
<div class="d-flex w-100 align-items-center justify-content-center bg-dark text-white" style="min-height: 16rem">Second slide</div>
<div class="carousel-caption d-none d-md-block">
<h5>Second slide label</h5>
<p>Some representative placeholder content for the second slide.</p>
</div>
</div>
<div class="carousel-item" data-label="Third slide">
<div class="d-flex w-100 align-items-center justify-content-center bg-secondary text-white" style="min-height: 16rem">Third slide</div>
<div class="carousel-caption d-none d-md-block">
<h5>Third slide label</h5>
<p>Some representative placeholder content for the third slide.</p>
</div>
</div>
</div>
</div> Dark controls
Set data-bs-theme="dark" on the carousel (or a parent) so captions and control icons invert. .carousel-dark is deprecated in Bootstrap 5.3.
<div class="carousel slide" data-bs-theme="dark">
<div class="carousel-inner">
<div class="carousel-item" data-label="First slide">
<div class="d-flex w-100 align-items-center justify-content-center bg-body-secondary" style="min-height: 16rem">First slide</div>
<div class="carousel-caption d-none d-md-block">
<h5>First slide label</h5>
<p>Some representative placeholder content for the first slide.</p>
</div>
</div>
<div class="carousel-item" data-label="Second slide">
<div class="d-flex w-100 align-items-center justify-content-center bg-secondary text-white" style="min-height: 16rem">Second slide</div>
<div class="carousel-caption d-none d-md-block">
<h5>Second slide label</h5>
<p>Some representative placeholder content for the second slide.</p>
</div>
</div>
<div class="carousel-item" data-label="Third slide">
<div class="d-flex w-100 align-items-center justify-content-center bg-dark text-white" style="min-height: 16rem">Third slide</div>
<div class="carousel-caption d-none d-md-block">
<h5>Third slide label</h5>
<p>Some representative placeholder content for the third slide.</p>
</div>
</div>
</div>
</div> Accessibility
Put data-label on each .carousel-item so ::scroll-marker has an accessible name. Native scroll buttons use content: "" / "Previous" and "Next".
On <pe-carousel>, the current pip is aria-current="true". Previous and next disable at the ends. scroll-behavior: smooth follows prefers-reduced-motion.
Browser support
| No script | Polyfill | No fallback |
|---|---|---|
scroll-snap-type is widely available. Swipe works. | None | ::scroll-button and ::scroll-marker: Chrome 135+ only. Optional pe-carousel covers buttons and pips elsewhere. No autoplay, fade, or wrap. |
Browsers without scroll buttons
<pe-carousel> is the carousel, with
class="carousel". Load
src/behaviors/carousel.js only on pages that use it.
Where ::scroll-button exists, the element does nothing:
the generated buttons and pips stay, and the HTML controls are
hidden. Where it does not, the two buttons scroll one
.carousel-item via
command="--prev" and
command="--next".
commandfor is the .carousel-inner id, with
no hash. A mouse drag pans that scroller; touch scrolling stays
native. Links inside a slide still click. Indicator buttons use
command="--slide" in the same order as the
slides. The current one is aria-current="true".
Fallback controls
In Chrome the HTML buttons and pips are hidden and the generated controls remain. In Firefox and Safari the buttons move one slide, the pips jump to a slide, and dragging with the mouse pans.
<script type="module" src="./node_modules/bootstrap-html/src/behaviors/carousel.js"></script>
<pe-carousel class="carousel slide">
<button type="button" class="carousel-control-prev" command="--prev" commandfor="fallback-carousel">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<div class="carousel-inner" id="fallback-carousel">
<div class="carousel-item" data-label="Slide 1">
<div class="d-flex w-100 align-items-center justify-content-center bg-secondary text-white" style="min-height: 16rem">Fallback first slide</div>
</div>
<div class="carousel-item" data-label="Slide 2">
<div class="d-flex w-100 align-items-center justify-content-center bg-dark text-white" style="min-height: 16rem">Fallback second slide</div>
</div>
<div class="carousel-item" data-label="Slide 3">
<div class="d-flex w-100 align-items-center justify-content-center bg-secondary text-white" style="min-height: 16rem">Fallback third slide</div>
</div>
</div>
<button type="button" class="carousel-control-next" command="--next" commandfor="fallback-carousel">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
<div class="carousel-indicators">
<button type="button" command="--slide" commandfor="fallback-carousel" aria-label="Slide 1"></button>
<button type="button" command="--slide" commandfor="fallback-carousel" aria-label="Slide 2"></button>
<button type="button" command="--slide" commandfor="fallback-carousel" aria-label="Slide 3"></button>
</div>
</pe-carousel>