← Back

CSS Carousels

Chrome 135 introduces new CSS primitives that make building accessible carousels dramatically easier—no JavaScript required. Pinterest tested these features and reduced their carousel code from 2,000 lines to just 200.

Read more on Chrome Developers →

Experimental Feature: CSS Carousels require Chrome 135+ or Edge 135+. Firefox and Safari do not yet support these features.

Base Scroller Setup

Start with a scrollable container using scroll-snap-type for smooth snapping behavior. Hide the scrollbar while maintaining scroll functionality.

.carousel {
  display: flex;
  gap: 1rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.carousel > li {
  scroll-snap-align: center;
  scroll-snap-stop: always;
}

Scroll Buttons

The ::scroll-button() pseudo-element generates accessible navigation buttons. The browser handles ARIA roles, tab order, and automatically disables buttons at scroll boundaries.

.carousel::scroll-button(left) {
  content: "" / "Previous slide";
}

.carousel::scroll-button(right) {
  content: "" / "Next slide";
}

/* Style disabled state */
.carousel::scroll-button(*):disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

Scroll Markers

Generate dot indicators using ::scroll-marker. First, opt in with scroll-marker-group on the scroller, then define markers on each item.

.carousel {
  /* Position markers after the scroller */
  scroll-marker-group: after;
}

.carousel > li::scroll-marker {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.3);
}

/* Group container styling */
.carousel::scroll-marker-group {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
}

Active State with :target-current

Style the currently snapped item's marker using the :target-current pseudo-class.

.carousel > li::scroll-marker:target-current {
  background: white;
  transform: scale(1.2);
}

Interactive Demo

This demo shows how CSS carousels work. In Chrome 135+, the navigation buttons and dots are generated entirely with CSS pseudo-elements. For other browsers, this demo uses JavaScript fallbacks.

Note: In Chrome 135+, the buttons and dots above would be replaced by native CSS pseudo-elements (::scroll-button and ::scroll-marker) with no JavaScript required.

::scroll-button()

Browser-generated prev/next buttons. Automatically disabled at scroll boundaries.

::scroll-marker

Dot indicators for each item. Click to jump directly to that slide.

:target-current

Style the active marker when its corresponding item is snapped into view.

Key Benefits

  • Native accessibility: Browser handles ARIA roles, keyboard navigation, and tab order automatically.
  • Zero JavaScript: All carousel functionality is CSS-only.
  • Stateful buttons: Scroll buttons auto-disable at boundaries.
  • Reduced code complexity: Pinterest reduced 2,000 lines to 200.

© 2026 rege. All rights reserved.