Back

Building Smooth Carousels with Pure CSS

Building Smooth Carousels with Pure CSS

For years, creating carousels meant reaching for JavaScript libraries like Swiper or Glide. Each added kilobytes to your bundle, introduced dependencies, and required careful management of event listeners and state. But modern CSS has quietly evolved to handle carousels natively—no JavaScript required.

This article explores how to build performant, accessible CSS carousels using scroll-snap, experimental pseudo-elements, and other pure CSS techniques that work today and will remain relevant for years to come.

Key Takeaways

  • Scroll-snap properties transform any scrollable container into a smooth carousel experience
  • Experimental pseudo-elements in Chrome 135+ enable native navigation controls without JavaScript
  • Pure CSS carousels improve performance by eliminating JavaScript parsing and event listeners
  • Fallback strategies ensure compatibility while preparing for future browser features

The Foundation: Scroll Snap CSS

The cornerstone of any pure CSS carousel is the scroll-snap property. It transforms a scrollable container into a paginated experience with just a few lines:

.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
}

.carousel-item {
  flex: 0 0 100%;
  scroll-snap-align: center;
}

This creates a horizontal carousel where each item snaps into place. The mandatory keyword ensures users always land on a complete slide, while scroll-behavior: smooth adds fluid transitions between items.

Scroll Snap CSS works across all modern browsers, making it the most reliable foundation for JavaScript-free carousels. For responsive designs, combine it with CSS Grid or adjust the flex-basis to show multiple items per view:

.carousel-item {
  flex: 0 0 calc(33.333% - 1rem);
  margin: 0 0.5rem;
}

Modern Navigation: Experimental Pseudo-Elements

Chrome 135+ introduces experimental pseudo-elements that generate carousel controls automatically. These features require enabling experimental web platform features in Chrome flags.

The ::scroll-button pseudo-element creates previous/next buttons without additional markup:

.carousel::scroll-button(inline-start),
.carousel::scroll-button(inline-end) {
  background: rgba(0, 0, 0, 0.5);
  color: white;
  padding: 1rem;
  border: none;
}

.carousel::scroll-button(inline-start)::before {
  content: "←";
}

.carousel::scroll-button(inline-end)::before {
  content: "→";
}

Similarly, ::scroll-marker generates pagination dots for each scrollable section:

.carousel {
  scroll-marker-group: after;
}

.carousel-item::scroll-marker {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #ccc;
}

.carousel-item::scroll-marker:target-current {
  background: #333;
}

Note that these pseudo-elements are experimental and their syntax may change. The :target-current pseudo-class highlights the active marker as users scroll. These features reduce JavaScript requirements while providing native accessibility support.

Fallback Strategy for Production

Since scroll-button and scroll-marker have limited browser support, use feature detection with @supports:

/* Fallback navigation */
.carousel-nav {
  display: flex;
  gap: 0.5rem;
}

/* Hide fallback when native controls are supported */
@supports (scroll-button-inline: both) {
  .carousel-nav {
    display: none;
  }
}

For broader compatibility, combine scroll-snap with anchor links for navigation:

<nav class="carousel-nav">
  <a href="#slide1">1</a>
  <a href="#slide2">2</a>
  <a href="#slide3">3</a>
</nav>

<div class="carousel">
  <div id="slide1" class="carousel-item">...</div>
  <div id="slide2" class="carousel-item">...</div>
  <div id="slide3" class="carousel-item">...</div>
</div>

Creating a CSS autoplay carousel requires only keyframe animations:

@keyframes slide {
  0%, 20% { transform: translateX(0); }
  25%, 45% { transform: translateX(-100%); }
  50%, 70% { transform: translateX(-200%); }
  75%, 95% { transform: translateX(-300%); }
  100% { transform: translateX(0); }
}

.autoplay-carousel {
  display: flex;
  animation: slide 12s infinite;
}

.autoplay-carousel:hover {
  animation-play-state: paused;
}

This technique works reliably but trades user control for simplicity. Pausing on hover improves usability, and combining it with prefers-reduced-motion respects accessibility preferences:

@media (prefers-reduced-motion: reduce) {
  .autoplay-carousel {
    animation: none;
  }
}

Accessibility and Performance

Pure CSS carousels excel at performance—no JavaScript parsing, no event listeners, no layout thrashing. The browser’s native scrolling handles touch, keyboard, and mouse inputs automatically.

For accessibility, ensure:

  • Scroll-snap provides smooth scrolling, but full keyboard navigation and accessible controls require the new ::scroll-button and ::scroll-marker features.
  • Focus indicators remain visible
  • Content remains accessible when CSS fails
  • Touch targets meet 44×44px minimum size

Add ARIA labels to improve screen reader experience:

<div class="carousel" role="region" aria-label="Product Gallery">
  <div class="carousel-item" aria-label="Slide 1 of 3">...</div>
</div>

Conclusion

Modern CSS has eliminated the need for JavaScript carousel libraries in most cases. Scroll-snap provides the foundation today, while emerging features like scroll-button and scroll-marker preview a future where complex UI components require zero JavaScript. These techniques reduce bundle sizes, improve performance, and simplify maintenance—benefits that compound over time.

Start with scroll-snap for production sites. Experiment with the new pseudo-elements in Chrome. Most importantly, question whether you need that 50KB carousel library when 20 lines of CSS achieve the same result.

FAQs

Yes, scroll-snap properties have excellent browser support across all modern browsers. The experimental pseudo-elements should only be used with proper fallbacks since they currently work only in Chrome 135+ with flags enabled.

Scroll-snap automatically supports touch gestures through native browser scrolling. Users can swipe naturally between slides, and the mandatory snap points ensure they always land on complete slides without additional code.

CSS carousels continue working perfectly when JavaScript is disabled since they rely entirely on native CSS properties. Users can still navigate using touch, mouse, or keyboard, making them more resilient than JavaScript-based solutions.

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay