Create a Carousel using CSS Scroll Snap and JavaScript

This tutorial will help you create a responsive carousel using the CSS scroll-snap property and very little JavaScript for navigation. You can use it for products display, or as a gallery, or for your testimonials – like the example in this tutorial. So here is what we will be creating:

carousel example

This is really just a horizontal scroll section but when you scroll, it “snaps” (auto-adjusts the position of the scroll container) to ensure it displays the full item – making it behave like a carousel. You can also use the arrows to display next and previous items, which requires JavaScript. You need to have some basic knowledge of HTML, CSS and JavaScript to follow along. On the way, you will learn about the CSS scroll-snap properties. Let’s get started.

Your Designer Toolbox
Unlimited Downloads: 500,000+ Web Templates, Icon Sets, Themes & Design Assets


Setting up

Create a blank HTML document and name it index.html. Add the basic HTML skeleton. If you use Visual Studio Code, all you need to do is type “!” and hit enter. You will end up with this.

<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title>
<body> </body>

The font used in this demo is ‘Noto Sans’. Let’s embed this font from Google fonts. Add this CDN link below the title tag to embed regular and italic styles of this font.

<link href=",[email protected],400;0,700;1,400&display=swap" rel="stylesheet">

Create your stylesheet and name it style.css. Link the stylesheet to your HTML document below the Google fonts CDN link using

<link rel="stylesheet" href="style.css">

I have downloaded three portrait images from Pexels and cropped them square for this demo. You can choose to include your own. Name them testimonial1.jpg, testimonial2.jpg and testimonial3.jpg.

Creating a basic scroller

Let’s first create a simple horizontal scroller with three items with width same as that of the scroll container.


Add this markup within body tag in your HTML file:

<div class="testimonials"> <div class="scroller"> <div class="item"> Item 1 </div> <div class="item"> Item 2 </div> <div class="item"> Item 3 </div> </div>

Here, the testimonials div acts as the wrapper, within which is the horizontal scroller, and then three item divs.


In style.css, begin with some common styles for all elements:

* { margin: 0; padding: 0; box-sizing: border-box;

Add these styles to the body element:

body { font-family: 'Noto Sans', sans-serif; font-size: 1em; color: #4A5568;

Restrict the width of the testimonials wrapper to about 800px and center it.

.testimonials { max-width: 800px; margin: auto;

Add these basics styles to see the scroller at work.

.scroller { overflow-x: scroll; display: flex;
.item { min-width: 100%; min-height: 200px; /* To start with */ background-color: #EDF2F7; /* For demo */

With this, you can see a horizontal scroll section like this.

carousel beginnings - horizontal scroll section

Scroll Snapping

With CSS scroll snap, it is possible to enforce the scroll position (smoothly moving to the enforced position) once the user stops scrolling. This works by applying two primary properties – scroll-snap-type and scroll-snap-align.

The scroll-snap-type property is applied on the parent container (In our case, it’s the scroller div). It takes two arguments – the snap direction ( x | y | both ) and the snap behavior ( mandatory | proximity ). We need to use x for the direction because we are scrolling horizontally. For the behavior, mandatory means the browser has to snap to the position, while proximity is less strict. We will use mandatory.

The scroll-snap-align property is applied to the child items. It determines the snap point. It takes one or two arguments for x or/and y direction ( none | start | end | center ). In our case, this property can have any value except for none, because our items are 100% width of the parent container. You can dig deeper into the CSS Scroll Snap concept here.

Add these properties to the scroller and item divs.

.scroller { /* Existing styles here */ scroll-snap-type: x mandatory;
.item { /* Existing styles here */ scroll-snap-align: center;

That’s all you need for the magic! Now check your browser and scroll horizontally. You will notice that the browser strictly snaps to display a full item once you stop scrolling at any point. With this, the primary carousel behavior is achieved.

Adding content

Let’s add some content to the items.


<div class="item"> <img src="testimonial1.jpg" alt="Elise"> <div class="card"> <p> Eu rebum molestie per. No nostrud imperdiet abhorreant qui, hinc incorrupte vix ad. Vel id aliquip oblique. Primis feugait sit an, brute illud convenire duo ea. Graece tempor eripuit sed in. </p> <span>Elise</span> </div>
<div class="item"> <img src="testimonial2.jpg" alt="John"> <div class="card"> <p> Vitae pericula maluisset ut mei, pro eleifend gubergren eu. Et his brute graeci. Affert ponderum ei vel, dolorum accumsan ea has. Sea oblique salutatus ei, simul lucilius pri et. Errem melius temporibus ut eos </p> <span>John</span> </div>
<div class="item"> <img src="testimonial3.jpg" alt="Imani"> <div class="card"> <p> Ne est virtute indoctum, quaeque vituperata cum ut. Te nisl quaeque pri. Vix ex autem latine, mel ne nobis scaevola, ei est dolor utinam commune. Pri unum doctus in, cu primis pertinax eos. </p> <span>Imani</span> </div>


Time to style it up.

.testimonials { /* Existing styles here */ padding: 15px; text-align: center;
.item { /* Existing styles here */ background-color: white; /* Change the value to white */ margin-bottom: 10px; padding: 0 50px;
.item img { margin: 15px auto -60px; width: 120px; height: 120px; border: solid 4px #ffffff; border-radius: 60px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); z-index: 2;
.card { background-color: rgb(237, 242, 247); padding: 80px 40px 40px; border-radius: 10px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); z-index: 1;
.card p { font-style: italic; line-height: 1.6;
.card span { display: block; margin-top: 20px; color: teal; font-weight: bold; letter-spacing: 0.05em; text-transform: uppercase;

Our carousel has taken shape. The only thing pending now is the navigation functionality.

Adding navigation

In this demo, I have added simple arrows with < and > symbols for the navigation buttons. You can also use icons or images.


Add these two lines immediately after the scroller div, but within the testimonial div:

<span class="btn prev">&lt;</span>
<span class="btn next">&gt;</span>


Add these to your stylesheet for positioning and styling the navigation buttons:

.testimonials { /* Existing styles here */ position: relative;
.testimonials .btn { position: absolute; top: 50%; margin-top: 20px; height: 30px; width: 30px; border-radius: 15px; background-color: rgba(95, 106, 117, 0.3); z-index: 1; line-height: 30px; text-align: center; color: white; font-weight: bold;
.testimonials .btn:hover{ background-color: rgba(95, 106, 117, 0.5); cursor: pointer;
.testimonials { right: 15px;
.testimonials .btn.prev { left: 15px;


We need some JavaScript to make the buttons work. Add this script before closing the body tag.

<script> const testimonials = document.querySelector('.testimonials'); const scroller = testimonials.querySelector('.scroller'); const nextBtn = testimonials.querySelector(''); const prevBtn = testimonials.querySelector('.btn.prev'); const itemWidth = testimonials.querySelector('.item').clientWidth; nextBtn.addEventListener('click', scrollToNextItem); prevBtn.addEventListener('click', scrollToPrevItem); function scrollToNextItem() { scroller.scrollBy({left: itemWidth, top: 0, behavior: 'smooth'}); } function scrollToPrevItem() { scroller.scrollBy({left: -itemWidth, top: 0, behavior: 'smooth'}); }

In this code above, we have added ‘click’ event handlers to the prev and next buttons. The functionality of moving to the previous or next item is achieved using the scrollBy method. Check out the syntax and examples of element.scrollBy method.

The option behavior: ‘smooth’ gives a smooth sliding effect. Please note that this effect is not supported in IE 11 and a few older versions of other browsers.

Making it loop

You can optionally make this carousel loop – clicking on ‘next’ button for the last item makes the carousel start from beginning and similar behavior for ‘prev’ button, by changing the methods in your script to this:

function scrollToNextItem() { if(scroller.scrollLeft < (scroller.scrollWidth - itemWidth)) // The scroll position is not at the beginning of last item scroller.scrollBy({left: itemWidth, top: 0, behavior:'smooth'}); else // Last item reached. Go back to first item by setting scroll position to 0 scroller.scrollTo({left: 0, top: 0, behavior:'smooth'});
function scrollToPrevItem() { if(scroller.scrollLeft != 0) // The scroll position is not at the beginning of first item scroller.scrollBy({left: -itemWidth, top: 0, behavior:'smooth'}); else // This is the first item. Go to last item by setting scroll position to scroller width scroller.scrollTo({left: scroller.scrollWidth, top: 0, behavior:'smooth'});

Making it fully responsive

What we have now is already responsive. Except that, for browser widths smaller than 480px, the card becomes too narrow and long. So let’s reduce some padding for the item and card for this width using media queries.

Add this to your stylesheet:

@media screen and (max-width: 480px) {
.item {
padding: 0 30px;
.card {
padding: 80px 30px 30px;

This is it! You have successfully created a responsive carousel for your testimonials section with the least amount of JavaScript, and also learned about the new scroll-snap properties in CSS. You can modify this carousel to suit your needs. Try and explore using this carousel for images with varying width and watch how it behaves.

Here is the complete source code for you to download.

Download Source Code

This post may contain affiliate links. See our disclosure about affiliate links here.