How to create a carousel with Tailwind CSS and Alpinejs

Michael Andreuzza
4 min readMay 2, 2024

--

Yes, a carousel, we are building a carousel with Tailwind CSS and Alpine.js.

See it live and get the code

Why a carousel?

A carousel is a type of slider that displays a series of images or content in a continuous loop. It is commonly used in web design to showcase multiple images or content in a single page. The carousel allows users to easily navigate through the content and view it in a visually appealing way.

Use cases:

  • Product listings: A carousel can be used to showcase a series of products in a single page, allowing users to easily navigate through the products and view them in a visually appealing way.
  • Blog posts: A carousel can be used to showcase a series of blog posts in a single page, allowing users to easily navigate through the blog posts and view them in a visually appealing way.
  • News articles: A carousel can be used to showcase a series of news articles in a single page, allowing users to easily navigate through the news articles and view them in a visually appealing way.
  • Image galleries: A carousel can be used to showcase a series of images in a single page, allowing users to easily navigate through the images and view them in a visually appealing way.
  • Video galleries: A carousel can be used to showcase a series of videos in a single page, allowing users to easily navigate through the videos and view them in a visually appealing way.

Let’s get started with understanding the code:**

The wrapper

  • x-data="{ skip: 1, atBeginning: false, atEnd: false, next() {this.to((current, offset) => current + (offset * this.skip))},: This is the data that will be used to store the state of the carousel.
  • prev() {this.to((current, offset) => current - (offset * this.skip))},: This is the function that will be used to navigate to the previous slide.
  • to(strategy) {let slider = this.$refs.slider; let current = slider.scrollLeft; let offset = slider.firstElementChild.getBoundingClientRect().width; slider.scrollTo({ left: strategy(current, offset), behavior: 'smooth' })},: This is the function that will be used to scroll the carousel to the next or previous slide.
  • focusableWhenVisible: {'x-intersect:enter'() {this.$el.removeAttribute('tabindex')}, 'x-intersect:leave'() {this.$el.setAttribute('tabindex', '-1')},},: This is the object that will be used to store the focusable elements when the carousel is visible.
  • disableNextAndPreviousButtons: {'x-intersect:enter.threshold.05'() {let slideEls = this.$el.parentElement.children; if (slideEls[0] === this.$el) { this.atBeginning = true } else if (slideEls[slideEls.length-1] === this.$el) { this.atEnd = true } }, 'x-intersect:leave.threshold.05'() {let slideEls = this.$el.parentElement.children; if (slideEls[0] === this.$el) { this.atBeginning = false } else if (slideEls[slideEls.length-1] === this.$el) { this.atEnd = false } },},: This is the object that will be used to store the state of the carousel when the user scrolls the carousel.

The buttons

  • <button [@click](/click)="prev" x-show="atBeginning" tabindex="0">: This is the button that will be used to navigate to the previous slide.
  • <button [@click](/click)="next" x-show="atEnd" tabindex="0">: This is the button that will be used to navigate to the next slide.

The slides

  • <li role="option" x-bind="disableNextAndPreviousButtons"></li>: This is the slide that will be used to display the content of the carousel.

Classes are removed for brevity, but I’ll keep those classes relveant to the tutorial.

<div
x-data="{
skip: 1,
atBeginning: false,
atEnd: false,
next() {
this.to((current, offset) => current + (offset * this.skip))
},
prev() {
this.to((current, offset) => current - (offset * this.skip))
},
to(strategy) {
let slider = this.$refs.slider
let current = slider.scrollLeft
let offset = slider.firstElementChild.getBoundingClientRect().width
slider.scrollTo({ left: strategy(current, offset), behavior: 'smooth' })
},
focusableWhenVisible: {
'x-intersect:enter'() {
this.$el.removeAttribute('tabindex')
},
'x-intersect:leave'() {
this.$el.setAttribute('tabindex', '-1')
},
},
disableNextAndPreviousButtons: {
'x-intersect:enter.threshold.05'() {
let slideEls = this.$el.parentElement.children
// If this is the first slide.
if (slideEls[0] === this.$el) {
this.atBeginning = true
// If this is the last slide.
} else if (slideEls[slideEls.length-1] === this.$el) {
this.atEnd = true
}
},
'x-intersect:leave.threshold.05'() {
let slideEls = this.$el.parentElement.children
// If this is the first slide.
if (slideEls[0] === this.$el) {
this.atBeginning = false
// If this is the last slide.
} else if (slideEls[slideEls.length-1] === this.$el) {
this.atEnd = false
}
},
},
}">
<div
aria-labelledby="carousel-label"
role="region"
tabindex="0"
x-on:keydown.left="prev"
x-on:keydown.right="next">
<div >
<button
:class="{ 'opacity-50 ': atBeginning }"
:aria-disabled="atBeginning"
:tabindex="atEnd ? -1 : 0"
x-on:click="prev"
tabindex="0"
><span
aria-hidden="true"
class="mx-auto">
&larr;
</span></button
>
<button
        :class="{ 'opacity-50 ': atEnd }"
:aria-disabled="atEnd"
:tabindex="atEnd ? -1 : 0"
x-on:click="next"
tabindex="0"
><span
aria-hidden="true"
class="mx-auto">
&rarr;
</span></button
>
</div>
<ul
role="listbox"
aria-labelledby="carousel-content-label"
tabindex="0"
x-ref="slider">
<li
role="option"
x-bind="disableNextAndPreviousButtons">
<!-- Slide content goes here -->
</li>
<!-- More slides -->
</ul>
</div>
</div>

Conclusion

This is a simple carousel that can be used for any type of content, such as a product listing, blog posts, news articles, or image galleries. The code is easy to understand and the structure is clear. The use of Tailwind CSS and Alpine.js makes it easy to style the carousel and add interactivity. Remeber to make it as accessible as possible, and you’re good to go!

Hope you enjoyed this tutorial and have a great day!

--

--

Michael Andreuzza

UI Designer ∙ Front end dev ∙ Freelancer Building: ✺ http://lexingtonthemes.com Learning: Japanese