DEV Community

Emmanuel Chucks
Emmanuel Chucks

Posted on • Edited on

Modern accessible carousel (HTML, CSS, JS)

Back in the day, if you wanted to include an image carousel or content slider on your website, there was much pressure to reach for third-party libraries.

Old style carousels

They were notoriously hard to build, especially considering accessibility and keyboard navigation. Even using third-party libraries came with mixed results.

Thanks to new CSS features like scroll-snap, aspect-ratio and object-fit, you can now whip up a perfectly usable carousel in no time with minimal effort. Sprinkle in some modern ES6+ JavaScript to manage keyboard navigation and you are all set 🎉.

Basic HTML structure

Let's define a wrapper for our whole carousel and a scroll area that will hold our images.

<div class="carousel__wrapper">
  <div class="carousel__scroll-area">

    <!-- Placeholder images to work with -->
    <figure>
      <img src="https://picsum.photos/853/480?random=1" alt="" width="853" height="480">
      <figcaption>
        Place image caption here
      </figcaption>
    </figure>

    <figure>
      <img src="https://picsum.photos/853/480?random=2" alt="" width="853" height="480">
      <figcaption>
        Place image caption here
      </figcaption>
    </figure>

    <figure>
      <img src="https://picsum.photos/853/480?random=3" alt="" width="853" height="480" objectFit="cover" />
      <figcaption>
        Place image caption here
      </figcaption>
    </figure>

    <figure>
      <img src="https://picsum.photos/853/480?random=4" alt="" width="853" height="480" objectFit="cover" />
      <figcaption>
        Place image caption here
      </figcaption>
    </figure>

    <figure>
      <img src="https://picsum.photos/853/480?random=5" alt="" width="853" height="480">
      <figcaption>
        Place image caption here
      </figcaption>
    </figure>
    <!-- End of placeholder images -->

  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Consider using <picture> for delivering optimized and responsive images.

Some initial CSS

Let's add in a basic CSS reset and place our carousel in the center of the page for presentation sake.

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: system-ui, sans-serif;
}

html {
  height: 100%;
}

body {
  display: grid;
  place-items: center;
  min-height: 90%;
}
Enter fullscreen mode Exit fullscreen mode

Let's also make our carousel wrapper as wide as our images and make the scroll-area scrollable.

.carousel__wrapper {
  max-width: 853px;
}

.carousel__scroll-area {
  display: flex;
  overflow-x: scroll;
  max-width: 100%;
  aspect-ratio: 16 / 9;
}
Enter fullscreen mode Exit fullscreen mode

images scrolling by in a div container

Believe it or not, we now have a technically working carousel! But I know you didn't come here to see some images in a horizontal scrolling div.

Oh scroll-snap!

With the help of scroll-snap we can make our images snap into position and cover the scroll window when the user scrolls, all with native functionality, no CSS hackery or JavaScript wizardry required.

.carousel__scroll-area {
  /* ... */

  /* enforces children snapping on the x axis */
  scroll-snap-type: x mandatory;
}

figure {
  /* makes sure images always snap in turn */
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
Enter fullscreen mode Exit fullscreen mode

Images snapping horizontally on scroll

Mobile users can already scroll through our carousel by swiping. We've achieved so much with so little effort.

Carousel controls

Let's quickly add some buttons to make it easier to control the carousel, especially on larger displays.

<div class="carousel__wrapper">

  <!--  Left button  -->
  <button class="carousel__button carousel__button--left">
    <svg aria-hidden="true" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
      <path d="M256 504C119 504 8 393 8 256S119 8 256 8s248 111 248 248-111 248-248 248zM142.1 273l135.5 135.5c9.4 9.4 24.6 9.4 33.9 0l17-17c9.4-9.4 9.4-24.6 0-33.9L226.9 256l101.6-101.6c9.4-9.4 9.4-24.6 0-33.9l-17-17c-9.4-9.4-24.6-9.4-33.9 0L142.1 239c-9.4 9.4-9.4 24.6 0 34z"></path>
    </svg>
  </button>

  <!--  Right button  -->
  <button class="carousel__button carousel__button--right">
    <svg aria-hidden="true" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
      <path d="M256 8c137 0 248 111 248 248S393 504 256 504 8 393 8 256 119 8 256 8zm113.9 231L234.4 103.5c-9.4-9.4-24.6-9.4-33.9 0l-17 17c-9.4 9.4-9.4 24.6 0 33.9L285.1 256 183.5 357.6c-9.4 9.4-9.4 24.6 0 33.9l17 17c9.4 9.4 24.6 9.4 33.9 0L369.9 273c9.4-9.4 9.4-24.6 0-34z"></path>
    </svg>
  </button>

  <div class="carousel__scroll-area">

    <!-- Placeholder images to work with -->
    <!-- ... -->
    <!-- End of placeholder images -->

  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

A little JS to make them control the scroll position of our scroll area.

// We first grab our scroll area and carousel buttons
const carouselScrollArea = document.querySelector(".carousel__scroll-area");

const leftCarouselButton = document.querySelector(".carousel__button--left");

const rightCarouselButton = document.querySelector(".carousel__button--right");

// We then listen in for clicks and scroll the carousel accordingly
leftCarouselButton.addEventListener("click", () => scrollCarousel("left"));

rightCarouselButton.addEventListener("click", () => scrollCarousel("right"));

function scrollCarousel(direction) {
  if (direction === "left") {
    carouselScrollArea.scrollLeft -= carouselScrollArea.clientWidth;
  } else if (direction === "right") {
    carouselScrollArea.scrollLeft += carouselScrollArea.clientWidth;
  } else {
    console.error("Invalid direction");
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's also add keyboard navigation so users can control the carousel with the left and right arrow keys on their keyboard.

// ...

// We grab the whole carousel wrapper
const carouselWrapper = document.querySelector(".carousel__wrapper");

// We listen for keypresses and react based on which key was pressed
carouselWrapper.addEventListener("keydown", handleKeyDown);

function handleKeyDown(e) {
  if (event.key === "ArrowLeft") {
    scrollCarousel("left");
  } else if (event.key === "ArrowRight") {
    scrollCarousel("right");
  }
}
Enter fullscreen mode Exit fullscreen mode

Carousel scrollable by button and left and right arrow keys

Final touches

Our carousel is now feature-complete and fully functional but it's not pretty. We should add some styling to make it a little nicer.

First, our carousel buttons:

.carousel__wrapper {
  /* ... */

  /* So we can absolutely position our buttons on top of the carousel */
  position: relative;
}

.carousel__button {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 1;
  background: none;
  border: none;
  padding: 2rem 1rem;
  color: white;
}

.carousel__button--left {
  left: 0;
}

.carousel__button--right {
  right: 0;
}
Enter fullscreen mode Exit fullscreen mode

Next, our images, figures, and figcaptions:

figure {
  /* ... */

  /* So will can absolutely position our figcaption */
  position: relative;

  /* Makes sure each image covers the scroll window */
  max-width: 100%;
  flex-shrink: 0;
}

img {
  /* Make sure the images never get too large */
  max-width: 100%;
  max-height: 100%;
}

figcaption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0.5rem;
  font-style: italic;
  text-align: center;
  color: white;

  /* To make our white caption a little more legible on the image */
  background-image: linear-gradient(to top, rgb(0 0 0 / 0.5), transparent);
}
Enter fullscreen mode Exit fullscreen mode

One more line to make our scrolling buttery smooth.

.carousel__scroll-area {
  /* ... */
  scroll-behavior: smooth;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

The best part of our carousel is us embracing the platform and relying on native features to make it work. We get so much functionality baked in for free and only have to enhance the experience where necessary.

Share this on Twitter if you enjoyed it. Thanks.

Top comments (1)

Collapse
 
gorlanova profile image
Gorlanova

Thank you so much, nice and clean !