Emmanuel Chucks
Posted on February 1, 2022
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.
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>
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%;
}
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;
}
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;
}
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>
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");
}
}
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");
}
}
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;
}
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);
}
One more line to make our scrolling buttery smooth.
.carousel__scroll-area {
/* ... */
scroll-behavior: smooth;
}
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.
Posted on February 1, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.