← Back to all tutorials

How to create a carousel with Tailwind CSS and Alpinejs

#
Published and written on May 02 2024 by Michael Andreuzza

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

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="prev" x-show="atBeginning" tabindex="0">: This is the button that will be used to navigate to the previous slide.
  • <button @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 relevant 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

Did you like this tutorial? Please share it with your friends!

Get lifetime access to every theme available today for $199 $139 and own them forever. Plus, new themes, lifetime updates, use on unlimited projects and enjoy lifetime support.

— No subscription required!