Carousel
Description
Section titled “Description”.bp-carousel uses scroll-snap-type: x mandatory for native scroll snapping. In supporting browsers (Chrome 148+), ::scroll-marker renders dot indicators and ::scroll-button renders prev/next arrows — all in pure CSS with no JavaScript. The manual .bp-carousel__dots and .bp-carousel__controls elements act as fallback for older browsers.
Full-width slides
<style>.demo-carousel--feature { --carousel-slide-padding: var(--bp-space-8); --carousel-slide-min-height: 10rem; }.demo-carousel__slide--brand { --carousel-slide-bg: var(--bp-color-brand-subtle); color: var(--bp-color-brand); }.demo-carousel__slide--subtle { --carousel-slide-bg: var(--bp-color-bg-subtle); }.demo-carousel__inner { display: flex; align-items: center; justify-content: center; height: 100%; font-weight: 600;}</style><div class="bp-carousel demo-carousel--feature"><div class="bp-carousel__track" role="region" aria-label="Feature highlights" aria-live="polite" aria-atomic="false"> <div class="bp-carousel__slide demo-carousel__slide--brand" tabindex="0"> <div class="demo-carousel__inner">Slide 1 — Tokens</div> </div> <div class="bp-carousel__slide demo-carousel__slide--subtle" tabindex="0"> <div class="demo-carousel__inner">Slide 2 — Components</div> </div> <div class="bp-carousel__slide demo-carousel__slide--subtle" tabindex="0"> <div class="demo-carousel__inner">Slide 3 — Themes</div> </div></div><ul class="bp-carousel__dots" role="tablist" aria-label="Slides"> <li role="presentation"><button class="bp-carousel__dot is-active" role="tab" aria-label="Slide 1" aria-selected="true"></button></li> <li role="presentation"><button class="bp-carousel__dot" role="tab" aria-label="Slide 2" aria-selected="false" tabindex="-1"></button></li> <li role="presentation"><button class="bp-carousel__dot" role="tab" aria-label="Slide 3" aria-selected="false" tabindex="-1"></button></li></ul></div>Public API
Section titled “Public API”| Variable | Default | Description |
|---|---|---|
--carousel-gap | var(--bp-space-4) | Gap between slides |
--carousel-slide-min | 100% | Minimum slide width |
--carousel-radius | var(--bp-radius-lg) | Slide border radius |
--carousel-slide-bg | transparent | Slide background — set on carousel or per-slide |
--carousel-slide-padding | 0px | Slide inner padding |
--carousel-slide-min-height | auto | Slide minimum height |
Variants
Section titled “Variants”| Class | Description |
|---|---|
bp-carousel--multi | Partial next slide visible as scroll affordance |
bp-carousel--peek | Responsive peek — 1.2 slides at small, 1.8 at medium, 2.2 at wide containers |
Customization example
Section titled “Customization example”Multi-slide (peek)
<style>.demo-carousel--multi { --carousel-slide-min: 70%; --carousel-slide-padding: var(--bp-space-6); --carousel-slide-min-height: 8rem; --carousel-slide-bg: var(--bp-color-bg-subtle);}.demo-carousel__slide--brand { --carousel-slide-bg: var(--bp-color-brand-subtle); color: var(--bp-color-brand); }.demo-carousel__inner { display: flex; align-items: center; justify-content: center; height: 100%; font-weight: 600;}</style><div class="bp-carousel bp-carousel--multi demo-carousel--multi"><div class="bp-carousel__track" role="region" aria-label="Team members"> <div class="bp-carousel__slide demo-carousel__slide--brand" tabindex="0"> <div class="demo-carousel__inner">Card A</div> </div> <div class="bp-carousel__slide" tabindex="0"><div class="demo-carousel__inner">Card B</div></div> <div class="bp-carousel__slide" tabindex="0"><div class="demo-carousel__inner">Card C</div></div></div></div>Responsive peek
Section titled “Responsive peek”bp-carousel--peek automatically adjusts the visible slide count based on the carousel’s container width — no JavaScript, no media queries.
Responsive peek (resize the preview)
<style>.demo-carousel--peek { --carousel-slide-min-height: 10rem; --carousel-slide-bg: var(--bp-color-bg-subtle); --carousel-slide-padding: var(--bp-space-6);}.demo-carousel__slide--brand { --carousel-slide-bg: var(--bp-color-brand-subtle); color: var(--bp-color-brand); }.demo-carousel__inner { display: flex; align-items: center; justify-content: center; height: 100%; font-weight: 600;}</style><div class="bp-carousel bp-carousel--peek demo-carousel--peek"><div class="bp-carousel__track" role="region" aria-label="Features"> <div class="bp-carousel__slide demo-carousel__slide--brand" tabindex="0"> <div class="demo-carousel__inner">Tokens</div> </div> <div class="bp-carousel__slide" tabindex="0"> <div class="demo-carousel__inner">Components</div> </div> <div class="bp-carousel__slide" tabindex="0"> <div class="demo-carousel__inner">Theming</div> </div> <div class="bp-carousel__slide" tabindex="0"> <div class="demo-carousel__inner">Grid</div> </div></div></div>With cards
Section titled “With cards”.bp-carousel__slide is just a snap container — drop any component inside. Here using .bp-card with no extra styles needed on the slide itself.
Card slides
<style>.demo-carousel--cards { --carousel-slide-min-height: 16rem; }.demo-carousel--cards .bp-card { height: 100%; }</style><div class="bp-carousel demo-carousel--cards"><div class="bp-carousel__track" role="region" aria-label="Team members"> <div class="bp-carousel__slide" tabindex="0" role="group" aria-label="Slide 1 of 3"> <div class="bp-card"> <div class="bp-card__image"> <img src="https://placehold.co/600x200/dbeafe/2563eb?text=Design" alt="" role="presentation" /> </div> <div class="bp-card__header"> <h3 class="bp-card__title">Design Tokens</h3> </div> <div class="bp-card__body"> Semantic color, spacing, and typography tokens that adapt to light and dark mode. </div> <div class="bp-card__footer"> <a href="/tokens/tokens/" class="bp-btn bp-btn--sm">View tokens</a> </div> </div> </div> <div class="bp-carousel__slide" tabindex="0" role="group" aria-label="Slide 2 of 3"> <div class="bp-card"> <div class="bp-card__image"> <img src="https://placehold.co/600x200/dcfce7/16a34a?text=Components" alt="" role="presentation" /> </div> <div class="bp-card__header"> <h3 class="bp-card__title">CSS Components</h3> </div> <div class="bp-card__body"> Zero-JS accordion, carousel, modal, nav and more — built with modern CSS primitives. </div> <div class="bp-card__footer"> <a href="/components/button/" class="bp-btn bp-btn--sm">View components</a> </div> </div> </div> <div class="bp-carousel__slide" tabindex="0" role="group" aria-label="Slide 3 of 3"> <div class="bp-card"> <div class="bp-card__image"> <img src="https://placehold.co/600x200/fef3c7/d97706?text=Themes" alt="" role="presentation" /> </div> <div class="bp-card__header"> <h3 class="bp-card__title">Theming</h3> </div> <div class="bp-card__body"> Override any token at any scope. Dark mode, brand colors, and custom themes with pure CSS. </div> <div class="bp-card__footer"> <a href="/tokens/tokens/" class="bp-btn bp-btn--sm bp-btn--ghost">Learn more</a> </div> </div> </div></div></div> ✓ No axe violations tested 2026-05-11
WCAG criteria covered:
Accessibility
Section titled “Accessibility”- Track uses
role="region"+aria-labelfor screen reader context. - Slides should have
tabindex="0"to be keyboard-reachable. - Dot buttons are decorative —
aria-hidden="true"on the list is acceptable; or addaria-labelper dot. - Arrow-key scrolling works natively once the track is focused.
Browser APIs
Section titled “Browser APIs”| API | Availability | Used for | Without it | Polyfill |
|---|---|---|---|---|
scroll-snap-type | Widely available Baseline 2020 | Native slide snapping without JS | Free-scrolling track, no snapping | None needed |
IntersectionObserver | Widely available Baseline 2020 | Syncing dot .is-active state with visible slide | Dots don’t update on scroll | polyfill |
::scroll-marker | Newly available Baseline 2025 | CSS-native dot indicators — auto-sync with scroll, no JS | Manual .bp-carousel__dots with JS .is-active toggle | None needed |
::scroll-button | Newly available Baseline 2025 | CSS-native prev/next arrows | Manual .bp-carousel__controls buttons | None needed |
| Container Queries | Widely available Baseline 2023 | Multi-slide peek layout adjustments | Static slide widths | None needed |
Internals
Section titled “Internals”--_gap,--_slide-min,--_radius— resolved on.bp-carousel, do not set directly.--_bg,--_padding,--_height— resolved per-slide on.bp-carousel__slide, do not set directly.