Simple Carousel with Vanilla JS

The following article will walk you through the steps of creating a basic card/image carousel using HTML, CSS, and vanilla JS. Our goal will be to create a carousel that:

  • Displays/advances by 3 cards at a time
  • Is keyboard accessible and constructed with semantic HTML
  • Has upper and lower limits to the direction in which it slides

Before we dive into the javascript portion, let’s take a look at the HTML we’ll be using. Our carousel will have one “parent wrapper” that contains the list of cards as well as the control buttons. The actual carousel will be comprised of an unordered list of <li>’s, which we’ll use as our cards. I’m using a <ul> because I assume all of the child cards will have some sort of shared commonality or relationship, and a <ul> tag communicates that to screen readers much better than a stack of <div>s.

The last important thing to note is the “separation of concerns” we’re following. Rather than using class names to target elements in the future, the HTML below uses data attributes, such as “target” and “action,” with respective values. This approach reduces the risk of someday having a class name accidentally deleted or changed by someone else. People are more likely to leave a piece of markup alone if they don’t understand it, or if it looks important, which data attributes usually do.


Without dwelling on it for too long, let’s glance at the CSS we’ll need. On our parent class, it’s most important that the overflow is hidden, and that the width is explicitly set. That way the parent class acts as a mask for any underlying cards in the carousel, and we only see 3 at a time. I chose a fixed width below because I had already decided the card width was set at 200px, with a right-margin of 16px. (200 * 3) + (16 * 2) = 632px for the width of the parent container. Feel free to take a different approach to get to the point of masking the content 😁.

The button wrapper serves to position the buttons such that they’re flush with each side, vertically centered, and sitting on top of the carousel itself. The rest of the styles just make it so that the list of cards flows horizontally, is spaced nicely, and lacks the default list style of an HTML element.


And now for the moment you’ve been waiting for, the javascript! As with the above HTML and CSS, there are many ways to make a carousel. Below is just one of them. So if you see something you’d do differently, that’s totally cool and you should feel free to do so!

The first step we’ll take is to select the necessary elements using a common approach, the .querySelector() method, and our handy data attributes that we set up in the HTML. Note, however, that you don’t always have to start from the document root (document.querySelector()). It’s possible to start on any node in the DOM tree; and in fact, it’s a tiiiiiny bit more efficient to start on a nested node when you can, because then you don’t have to traverse the whole DOM to find the node you want. So that’s what we’ll do to select one of the cards in the carousel (see how we’re starting from carousel.querySelector() instead of document.querySelector() on line 4?).

Next, we need a few other pieces of information to help us slide the carousel in just the right way. The effect we want is for the carousel to advance by 3 cards at a time, and it should stop each time in the same position (no margin on either of the out sides, with all 3 cards perfectly visible). To do that, we need to know the width of the visible part of the carousel, and the margin-right property assigned to the cards (without accounting for the margin, you wind up with the cards sliding into a final position that’s just a little off).

One thing that’s a bit tricky in this step is the way we get these properties. Unfortunately you can’t do “carousel.width()” to get the width of the carousel. Instead, you must use carousel.offsetWidth. And for the card margin, it’s even trickier. You can only use “card.style.marginRight()” if you define an inline style in the HTML, which we didn’t do. We want the style we wrote in the separate stylesheet. To get that value, we have to do what’s written on line 12 below, which tries two different ways of getting the externally defined style object for the card element. The reason for the two approaches is that different browsers call this object different things, so we have to try both ways.

Once we get that object, we can target the marginRight property…but it’s a string, “16px”. So we have to do two things, use a regular expression to select just the numbers, then turn the string “16” into the number 16.

Moving along, we now need to set the boundaries for our carousel. While you could create an “infinite” carousel that repeats the same content, I’m choosing to go with a fixed approach, so the behavior we’re aiming for is when the page loads and there is no content on the left side of the carousel, the “Left” button won’t work, and when the user reaches the upper end of the carousel and there’s no more content hidden on the right side, the “Right” button won’t work.We’ll do this using two variables: offset and maxX.

Offset is a variable that will increment or decrement by (carouselWidth + cardMarginRight, or 648) every time we click one of the buttons. Offset becomes more positive when we click the left button (because we’ll be pushing the cards to the right by 648px at a time), and more negative when we click the right button (because we’ll be pulling the cards to the left by 648px at a time). When the page loads, we want the carousel to start right at the beginning, so offset is initialized to 0 (line 20).

To calculate the maxX property, we need to know how many times the carousel could slide by 648px. We find this out by first getting the total numbers of cards we have (“cardCount” on line 16); we know this by obtaining the length of “carousel.querySelectorAll()”, which returns a node list of all the cards. Once we know that, we factor in how many cards we are showing at a time(3) and multiply that by the width of the carousel, then we add (cardMarginRight * cardCount). I also had to do some unexpected subtraction so that the “Right” button would disable at the desired point. Without line 23, the “Right” button remains usable for one click too long, and the user can slide past the upper limit of the carousel. You’ll see what I mean if you don’t add line 23 to your maxX calculation.

Finally, we add the click events (lines 27–39). Each event listener includes logic that allows or prevents the carousel from sliding. If the conditions on lines 28 or 35 are true, we update “offset” by the carouselWidth and cardMarginRight (648). Then, we update the transform property of the carousel using `translateX(${offset})px`. Click here to learn about this syntax, which is called template literals (basically it let us use variables inside of strings).

In a previous version of this project, I was positioning the carousel absolutely and altering the “left” property. But a friend pointed out that unlike absolute values/properties, “transform” is hardware accelerated across browsers, and is therefore more performant. So that’s why we’re using that. Thanks Cameron!

And there you have it! A functioning carousel with simple JS. Some next steps I might take with this are to pull the callback functions out of each event listener so they could be reused in the future if necessary, or add navigational progress dots below the cards. I could also convert the carousel into its own class, or even a web component. If you have other ideas for improvement, feel free to comment below!

You can also check out the above code on Codepen.