Carousel
1
2
3
4
5
<div class="carousel" style="width: 16rem; margin: 0 3.5rem"> <div class="carousel-content" aria-label="Numbers" style="gap: 1rem"> <div class="carousel-item"> <div class="card" style="aspect-ratio: 1; display: grid; place-items: center"> <span style="font-size: 2.25rem; font-weight: 600">1</span> </div> </div> <div class="carousel-item"> <div class="card" style="aspect-ratio: 1; display: grid; place-items: center"> <span style="font-size: 2.25rem; font-weight: 600">2</span> </div> </div> <div class="carousel-item"> <div class="card" style="aspect-ratio: 1; display: grid; place-items: center"> <span style="font-size: 2.25rem; font-weight: 600">3</span> </div> </div> <div class="carousel-item"> <div class="card" style="aspect-ratio: 1; display: grid; place-items: center"> <span style="font-size: 2.25rem; font-weight: 600">4</span> </div> </div> <div class="carousel-item"> <div class="card" style="aspect-ratio: 1; display: grid; place-items: center"> <span style="font-size: 2.25rem; font-weight: 600">5</span> </div> </div> </div>
<button class="btn btn-outline btn-icon carousel-previous" type="button"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m15 18-6-6 6-6"></path> </svg> <span class="sr-only">Previous slide</span> </button> <button class="btn btn-outline btn-icon carousel-next" type="button"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m9 18 6-6-6-6"></path> </svg> <span class="sr-only">Next slide</span> </button></div><div class="carousel"> <div class="carousel-content" aria-label="Featured"> <div class="carousel-item">…</div> <div class="carousel-item">…</div> </div>
<button class="btn btn-outline btn-icon carousel-previous" type="button">…</button> <button class="btn btn-outline btn-icon carousel-next" type="button">…</button></div>| Class | Part |
|---|---|
.carousel | The wrapper the buttons are positioned against |
.carousel-content | The scroller |
.carousel-item | One slide |
.carousel-previous / .carousel-next | The buttons |
| Attribute | On | Meaning |
|---|---|---|
data-orientation="vertical" | .carousel | Scrolls down instead of across |
data-index | .carousel | Which slide is showing. Set by the runtime |
A slide is basis-full by default. Change the basis and several fit at once.
1
2
3
4
<div class="carousel" style="width: 24rem; margin: 0 3.5rem"> <div class="carousel-content" aria-label="Half-width slides" style="gap: 1rem"> <div class="carousel-item" style="flex-basis: 50%"> <div class="card" style="aspect-ratio: 1; display: grid; place-items: center"><span>1</span></div> </div> <div class="carousel-item" style="flex-basis: 50%"> <div class="card" style="aspect-ratio: 1; display: grid; place-items: center"><span>2</span></div> </div> <div class="carousel-item" style="flex-basis: 50%"> <div class="card" style="aspect-ratio: 1; display: grid; place-items: center"><span>3</span></div> </div> <div class="carousel-item" style="flex-basis: 50%"> <div class="card" style="aspect-ratio: 1; display: grid; place-items: center"><span>4</span></div> </div> </div> <button class="btn btn-outline btn-icon carousel-previous" type="button"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m15 18-6-6 6-6"></path> </svg> <span class="sr-only">Previous slide</span> </button> <button class="btn btn-outline btn-icon carousel-next" type="button"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m9 18 6-6-6-6"></path> </svg> <span class="sr-only">Next slide</span> </button></div>Vertical
Section titled “Vertical”1
2
3
<div class="carousel" data-orientation="vertical" style="width: 12rem; margin: 3.5rem 0"> <div class="carousel-content" aria-label="Vertical numbers" style="height: 12rem; gap: 1rem"> <div class="carousel-item"> <div class="card" style="height: 12rem; display: grid; place-items: center"><span>1</span></div> </div> <div class="carousel-item"> <div class="card" style="height: 12rem; display: grid; place-items: center"><span>2</span></div> </div> <div class="carousel-item"> <div class="card" style="height: 12rem; display: grid; place-items: center"><span>3</span></div> </div> </div> <button class="btn btn-outline btn-icon carousel-previous" type="button"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m15 18-6-6 6-6"></path> </svg> <span class="sr-only">Previous slide</span> </button> <button class="btn btn-outline btn-icon carousel-next" type="button"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="m9 18 6-6-6-6"></path> </svg> <span class="sr-only">Next slide</span> </button></div>Events
Section titled “Events”document.addEventListener("pk:carousel:change", (event) => { console.log(event.detail.index, event.detail.atEnd);});Scroll snapping, not a slider library
Section titled “Scroll snapping, not a slider library”The movement is scroll-snap-type — the browser owns it. That means swipe on a phone, momentum on
a trackpad, the scrollbar if the user wants it, and scroll-behavior: smooth that goes instant
under reduced motion. The runtime only drives the buttons and reports which slide is showing;
remove the script and the carousel still scrolls.
Set dir="rtl" on the page or on any subtree — the buttons swap ends and the arrow keys follow
the reading direction. See the button page for a live example.
Accessibility
Section titled “Accessibility”- The region announces itself as a carousel, and each slide as a slide with its position — “2 of 5” — so the count is known without seeing it.
- The scroller is focusable and takes the arrow keys, so it can be moved through without a pointer and without reaching for the buttons.
- Name the scroller.
aria-labelon.carousel-contentsays what the set is; “carousel” alone says nothing. - The buttons disable at the ends rather than wrapping, so a keyboard user can tell where the set finishes.
- Off-screen slides are still in the DOM and still tabbable. If a slide holds links, that is what you want; if it holds a form, consider whether a carousel is the right shape at all.