// blog/developer/
Back to Blog
Developer · August 15, 2026 · 9 min read · Updated May 22, 2026

CSS Scroll Snap Carousels Without JavaScript

CSS Scroll Snap Carousels Without JavaScript

JavaScript carousel libraries (Swiper, Slick, Embla, Flickity) have run sliders on the web for over a decade. They work, but each one costs bundle size, init delay, accessibility patches, and the time to learn one more API.

CSS Scroll Snap handles the same core behavior natively. The browser manages snapping physics, touch gestures, and smooth scrolling, with performance that matches platform-native scroll.

You still need JavaScript for autoplay, pagination dots, lazy loading, or complex navigation. But for any carousel that just needs smooth horizontal scrolling with items snapping into place, CSS does the job in a fraction of the code.

* * *

The Core Scroll Snap Properties

CSS Scroll Snap works with two elements: the scroll container (parent) and the scroll items (children). The parent defines the snapping behavior, and the children define where to snap.

`css .carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; gap: 16px; }

.carousel-item { flex: 0 0 300px; scroll-snap-align: start; } `

scroll-snap-type: defines the axis (x or y) and strictness (mandatory or proximity). mandatory forces snapping to the nearest point after every scroll. proximity only snaps when the scroll position is near a snap point, allowing free scrolling between items.

scroll-snap-align: tells the browser where each item should align within the container. start aligns to the left edge, center centers the item, and end aligns to the right edge.

scroll-behavior: smooth: adds smooth scrolling animation when programmatically scrolling (like clicking a next/prev button). Touch scrolling is already smooth natively.

scroll-snap-stop: the less-known property. Setting scroll-snap-stop: always prevents fast flick gestures from skipping items. Without it, a strong swipe can skip past multiple items. Use it when each item needs to be seen individually.

Minify your carousel CSS with the CSS Minifier once you have finalized the styles.

Mobile phone showing a horizontal image carousel
Mobile phone showing a horizontal image carousel
* * *

Building a Responsive Image Carousel

Here is a complete responsive image carousel using only CSS:

`css .image-carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; gap: 16px; padding: 16px; -webkit-overflow-scrolling: touch; scrollbar-width: none; / Hide scrollbar Firefox / }

.image-carousel::-webkit-scrollbar { display: none; / Hide scrollbar Chrome/Safari / }

.image-carousel img { flex: 0 0 80vw; max-width: 600px; scroll-snap-align: center; border-radius: 12px; object-fit: cover; aspect-ratio: 16 / 9; }

@media (min-width: 768px) { .image-carousel img { flex: 0 0 45vw; } }

@media (min-width: 1024px) { .image-carousel img { flex: 0 0 30vw; } } `

This creates a carousel that shows one image on mobile (80% viewport width), two on tablet (45%), and three on desktop (30%). Items snap to center, scrollbar is hidden for a cleaner look, and the touch behavior is native and smooth.

The key responsive trick is using vw units for flex-basis instead of fixed pixels. This ensures the carousel adapts to any screen size without media query breakpoints for every possible width.

Key takeaway

Here is a complete responsive image carousel using only CSS: ```css .image-carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; gap: 16px; padding: 16px; -webkit-overflow-scrolling: touch; scrollbar-width: none; /* Hide scrollbar Firefox */ } .image-carousel::-webkit-scrollbar { display: none; /* Hide scrollbar Chrome/Safari */ } .image-carousel img { flex: 0 0 80vw; max-width: 600px; scroll-snap-align: center; border-radius: 12px; object-fit: cover; aspect-ratio: 16 / 9; } @media (min-width: 768px) { .image-carousel img { flex: 0 0 45vw; } } @media (min-width: 1024px) { .image-carousel img { flex: 0 0 30vw; } } ``` This creates a carousel that shows one image on mobile (80% viewport width), two on tablet (45%), and three on desktop (30%).

* * *

Card Carousels and Product Sliders

Product cards in a horizontal scroll are one of the most common UI patterns. Here is how to implement it with Scroll Snap:

`css .product-carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; gap: 20px; padding: 20px; scroll-padding: 20px; }

.product-card { flex: 0 0 280px; scroll-snap-align: start; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); overflow: hidden; } `

Note the scroll-padding property on the container. This accounts for the container's padding when calculating snap positions. Without it, the first visible item snaps to the very edge of the container, ignoring the padding.

Peeking next item: a common design pattern is showing a sliver of the next item to hint that the carousel is scrollable. Achieve this by making items slightly narrower than the visible area:

`css .product-card { flex: 0 0 calc(100% - 60px); / Shows 60px of next item / }

@media (min-width: 768px) { .product-card { flex: 0 0 calc(50% - 30px); / Two items with peek / } } `

Format your code with the Code Formatter to keep the CSS clean and maintainable as you build these patterns.

* * *

Adding Navigation with Minimal JavaScript

Pure CSS carousels work great with touch and mouse wheel scrolling, but most designs also need previous/next buttons. This requires a small amount of JavaScript, but far less than a full carousel library.

`javascript const carousel = document.querySelector('.carousel'); const prevBtn = document.querySelector('.carousel-prev'); const nextBtn = document.querySelector('.carousel-next');

const scrollAmount = carousel.querySelector('.carousel-item').offsetWidth + parseInt(getComputedStyle(carousel).gap);

prevBtn.addEventListener('click', () => { carousel.scrollBy({ left: -scrollAmount, behavior: 'smooth' }); });

nextBtn.addEventListener('click', () => { carousel.scrollBy({ left: scrollAmount, behavior: 'smooth' }); }); `

The scrollBy method combined with scroll-behavior: smooth on the container produces a smooth animated scroll, and the Scroll Snap CSS ensures the carousel lands exactly on the next item.

For pagination dots, you can use an Intersection Observer to detect which item is currently visible and highlight the corresponding dot:

`javascript const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const index = [...carousel.children].indexOf(entry.target); dots.forEach((dot, i) => dot.classList.toggle('active', i === index)); } }); }, { root: carousel, threshold: 0.5 });

carousel.querySelectorAll('.carousel-item').forEach(item => { observer.observe(item); }); `

This is about 20 lines of JavaScript versus the hundreds or thousands in a typical carousel library.

CSS code for scroll snap properties on dark editor background
CSS code for scroll snap properties on dark editor background
* * *

Accessibility and Performance

CSS Scroll Snap carousels have a natural accessibility advantage over JavaScript carousels: they are just scrollable containers. Screen readers and keyboard users can navigate them using standard scroll mechanics without needing custom ARIA roles or keyboard handlers.

However, there are still accessibility considerations:

  • Add role="region" and aria-label="Product carousel" to the container for screen reader context
  • Ensure navigation buttons have descriptive aria-label attributes ("Previous product" instead of just an arrow icon)
  • Do not hide the scrollbar on pages where the carousel is the primary content. The scrollbar provides a visual cue that there is more content. Hiding it is acceptable for supplementary carousels
  • Test with keyboard: users should be able to Tab into the carousel and use arrow keys to scroll

Performance benefits of CSS Scroll Snap:

  • Zero JavaScript for core scroll behavior means no main-thread blocking
  • The browser's native scroll implementation is GPU-accelerated
  • No layout recalculations during scroll (unlike JavaScript-based transform carousels)
  • Reduced bundle size: you eliminate an entire library dependency

Minify the final HTML structure with the HTML Minifier to keep the page weight minimal.

* * *

FAQ

Can I use CSS Scroll Snap for vertical carousels?

Yes. Change scroll-snap-type from x mandatory to y mandatory and set the container to overflow-y: auto instead of overflow-x: auto. Vertical scroll snap works well for full-page section scrolling and vertically scrolling card stacks.

Does CSS Scroll Snap work on all browsers?

Yes. CSS Scroll Snap has full support across all major browsers since 2019, including Chrome, Firefox, Safari, and Edge. The older scroll-snap-points syntax (deprecated) should not be used. The current scroll-snap-type and scroll-snap-align properties have universal support.

How do I add autoplay to a CSS Scroll Snap carousel?

Autoplay requires JavaScript. Use setInterval to call scrollBy on the container at regular intervals. Pause the interval on hover, focus, and touch to avoid fighting the user's scroll intent. Consider whether autoplay is actually necessary; it often harms more than it helps.

Can I animate the snap transition speed?

The scroll-behavior: smooth property controls the animation, but you cannot customize the duration or easing. The browser determines the animation curve. If you need precise control over the scroll animation, you will need JavaScript with requestAnimationFrame or a library like GSAP.

Key takeaway

### Can I use CSS Scroll Snap for vertical carousels.