Skip to content
Development3 min read

A Pure-CSS Carousel (No JavaScript Required)

You don't need a library, or even a line of JavaScript, to build a smooth, swipeable carousel. CSS scroll-snap handles the whole thing - here's how, with the code.

Otter
A Pure-CSS Carousel (No JavaScript Required)

Most carousels run on JavaScript - a library, a few event listeners, some state to track which slide you’re on. A lot of the time that’s more than the job needs. If all you want is a row of slides someone can swipe through, CSS can do the whole thing on its own with scroll-snap.

Here’s one built from nothing but CSS and a handful of divs. Swipe it on a phone, or drag with a trackpad:

No JavaScript anywhere. Here’s how it works.

The markup

Just a container and some slides:

<div class="slides-container">
  <div class="slide">1</div>
  <div class="slide">2</div>
  <div class="slide">3</div>
  <div class="slide">4</div>
  <div class="slide">5</div>
  <div class="slide">6</div>
</div>

That’s the whole structure. Everything else is CSS.

The idea

Four properties do the heavy lifting:

  • A flex row that doesn’t wrap, so the slides line up side by side instead of stacking.
  • Horizontal scrolling on the container.
  • scroll-snap-type on the container, so a scroll settles neatly on a slide instead of stopping halfway.
  • scroll-snap-stop on each slide, so one swipe moves one slide rather than flinging past several.

The CSS

.slides-container {
  display: flex;
  flex-flow: row nowrap; /* one row, no wrapping */
  overflow-x: auto; /* let it scroll sideways */
  scroll-snap-type: x mandatory; /* always settle on a slide */
  gap: 20px;
  height: 200px;
  scrollbar-width: none; /* hide the scrollbar in Firefox */
}

.slides-container::-webkit-scrollbar {
  display: none; /* and in Chrome / Safari */
}

.slides-container .slide {
  flex: none; /* keep each slide full width - don't shrink */
  width: 100%;
  height: 100%;
  scroll-snap-align: center; /* snap to the slide's center */
  scroll-snap-stop: always; /* one slide per swipe, no skipping */
  display: flex;
  align-items: center;
  justify-content: center;
  background: skyblue;
  color: #fff;
  font-size: 80px;
  font-weight: bold;
}

A few of those lines are worth calling out:

  • flex: none on the slides keeps each one at full width. Without it, flex would happily shrink them to fit, the row would never overflow, and there’d be nothing to scroll.
  • scroll-snap-type: x mandatory tells the browser to always snap to a slide on the x-axis. Pair it with scroll-snap-align: center and each slide settles dead center.
  • scroll-snap-stop: always is the one people miss. Without it, a hard swipe can blow past two or three slides at once. With it, every swipe advances exactly one.
  • Hiding the scrollbar is purely cosmetic - the scrolling still works, the bar’s just gone.

The one thing it won’t do

Because it’s built on scrolling, it responds to everything that scrolls: touch, trackpad, the scrollbar, even the arrow keys when it’s focused. The one gesture it doesn’t handle on its own is grabbing it with the mouse and dragging - click-and-drag needs a little JavaScript. For most carousels, nobody misses it.

When to reach for it

For image strips, logo rows, a row of testimonials - anything where swiping is the natural gesture - this is hard to beat. There’s no dependency to install, nothing to keep updated, and almost nothing to ship. Leaning on the platform like this is a big part of how you keep a site fast. Save the JavaScript for when you genuinely need it: autoplay, arrow buttons that jump to an exact slide, or that mouse-drag.

Back to all articles
Share

Keep Reading

Say hello

Let's make something
you're proud of.

Tell us what you're working on - even a rough idea is plenty. We read every message and reply like actual humans, usually within a day.

Prefer email?

[email protected]

Based in

Montenegro

Working with people here, and anywhere the wifi reaches.