Responsive Infinite Carousel / Slider Using React and Tailwind CSS

Responsive Infinite Carousel / Slider Using React and Tailwind CSS

Post by: Niraj Dhungana

Posted on: Nov 17, 2021

#React# Carousel# Slider

Nowadays brands use a slider which is also known as a carousel inside their website to incorporate much information in a reduced space.

They use a slider in a hero section to showcase their featured products or services. They also use a slider to showcase testimonials or some other useful places.

Why coding a slider from scratch?

There are a lot of packages available with lots of features like fade in, fade out, slide and bunch of other transition options to add a slider to your React or any type of app.

But hey, for me coding isn't just a job, it's a way to solve challenges and learn new things every time. So, without any further ado let's create a workable responsive infinite slider using React.

The slider we are going to create today is an infinite carousel where you will also get two buttons to control them. If you want to know more you can check out the first part of this video how exactly the traditional slider works.

Also you can check out the demo of the responsive React carousel which we are going to create in this post.

The things we are covering.

  • What is Tailwind CSS?
  • How to set up React and Tailwind CSS?
  • Creating an UI for carousel.
  • Writing logic to move slides back and forth.
  • Playing carousel infinitely.
  • Adding animations to our slides.
  • Pause slide on mouse enter.
  • Adding icons.

What is Tailwind CSS?

If you don’t know then tailwind css is a great css framework with a lot of flexibility and customizability. You can simply install it or use CDN links inside your project and you can start adding class names.

Yes it’s a class based framework where you will get some pre-written class names and also you can add your own and later we will see how.

The best thing about tailwind css is that it removes all the unused class names and styles at the production build. So, no unused pre-written css will bloat your final project.

How to set up React and Tailwind CSS

If you follow the official docs, setting up React and Tailwind CSS is a pretty straightforward task. So, let’s set up the project.

Or if you don't want to do that you can use this repo and skip the tailwind configuration part. Just download or clone this repo and do npm install.

Now I hope you already have some experience with node, command line and react itself. One more thing I am using npm, change it to yarn if you are using yarn.

1
npx create-react-app responsive-react-slider

After initializing our React app, let's install Tailwind CSS itself and make sure you are inside your project responsive-react-slider or whatever name that you chose.

To change directory you can type

1
cd responsive-react-slider // or the project name you chose

Then run this command

1
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

According to the documentation create-react-app doesn’t let us override the PostCSS configuration natively, we also need to install CRACO to be able to configure Tailwind.

1
npm install @craco/craco

Once you installed everything we need to change some script inside the package.json file.

1
2
3
4
// inside package.json
"start": "craco start",
"build": "craco build",
"test": "craco test",

You need to remove react-script from start, build and test script from your package.json file and replace with the craco, like above.

Now create a craco.config.js file and copy and paste the code from below.

1
2
3
4
5
6
7
8
9
10
module.exports = {
  style: {
    postcss: {
      plugins: [
        require('tailwindcss'),
        require('autoprefixer'),
      ],
    },
  },
}

Then we need to initialize the tailwind config file. So just run this command.

1
npx tailwindcss-cli@latest init

After running this command you will see a new file added to your project directory. So, open that tailwind.config.js and there you will see a property called purge with a value empty array. Just remove that and replace with this.

1
2
3
4
5
// tailwind.config.js
  module.exports = {
   purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
…
}

Also you can remove ts and tsx from the purge if you are not using TypeScript as your programming language.

After all this we need to go to the src/index.css file and the code from below. You can remove all default css that comes with create-react-app

1
2
3
4
/* ./src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

Now after all this we need to include this css inside our index.js and if you haven’t changed anything from the index.js file then it is already there.

1
2
3
4
5
// ./src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";

Now after all this we need to include this css inside our index.js and if you haven’t changed anything from the index.js file then it is already there.

Creating an UI for carousel

Now we are done with Tailwind CSS. Let’s create an UI for our React slider. For this project I am going to write everything inside App.js. But feel free to create new components and files if you want to.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const featuredImages = ['img-1', 'img-2', 'img-3'];
export default App(){
return (
    <div className="max-w-screen-xl m-auto">
      <div className="w-full relative select-none">
        <img src={featuredImages[0]} alt="" />

        <div className="absolute w-full top-1/2 transform -translate-y-1/2 flex justify-between items-start px-3">
          <button>Previous</button>
          <button>Next</button>
        </div>
      </div>
    </div>
  );
}

Even though all the codes are speaking on their own, let me explain it again.

Here first of all we are wrapping everything inside a div with class max-w-screen-xl and mx-auto. Meaning we just want to assign it a max width of 1280px and we want it in the middle in the X-axis.

You can find out more about max-width here.

Then we have a div with width full of it’s parent (1280px), position relative and select none.

Inside that div we have an image tag with the source of the first index from featuredImages. This featuredImages is an array with 3 image links which you can get for free from unsplash.com. If you want to use the same images one, two, three.

Below image I have placed a div with position absolute because I want to make it center from the top of the relative div which is above. And for that I am using some transform and translate classes.

Then I am using display flex, and justify-between to align the two buttons on the two corners which we have inside that div.

Now your project must look like this when you run npm start in the terminal.

result of the unfinished and unresponsive react slider

Which is not responsive at all and if you notice the buttons are out of the container. Let’s make it responsive and fix the issue.

To make images responsive inside Tailwind CSS we can use a plugin called @tailwindcss/aspect-ratio. You can use this command to install this plugin.

1
npm i @tailwindcss/aspect-ratio

Now after that you need to configure this plugin inside your tailwind-config file. Simply open your tailwind.config.js file and add this required command inside the plugins section.

1
2
3
4
5
// tailwind.config.js
module.exports = {
...
    plugins: [require("@tailwindcss/aspect-ratio")],
}

Now if you just wrap your image inside a div with two classes aspect-w-16 and aspect-h-9 and run your project you will get a completely responsive image with aspect ratio 16:9. You can learn more about aspect ratio here.

responsinve image after using tailwind aspect ratio plugin

1
2
3
4
5
6
7
8
9
export default App(){
    return (
        <div className="max-w-screen-xl m-auto">
            <div className="w-full relative select-none">
                <div className="aspect-w-16 aspect-h-9">
                    <img src={featuredImgs[0]} alt="" />
                </div>
...
)}

Writing logic to move slides back and forth

Ok, the UI is done, let's write some logic to move our slide. For that I will add click listeners to the buttons.

1
2
3
4
5
6
7
8
9
10
11
12
export default App(){
    const handleOnNextClick = () => {};

    const handleOnPrevClick = () => {};

    return (
        <div className="max-w-screen-xl m-auto">
            ...
            <button onClick={handleOnPrevClick}>Previous</button>
            <button onClick={handleOnNextClick}>Next</button>
            ...
)}

Also don’t forget to create those methods attached to those buttons. Now before we move forward let’s talk about how first.

The plan is. Whenever we receive the on click listener to the buttons we want to change the image source.

1
2
3
...
    <img src={featuredImages[0]} alt="" />
...

Like here we are using featuredImages[0] it’s a hardcoded value write instead of that lets create a state called currentIndex and use the value from that state.

1
2
3
4
5
6
7
export default function App() {
        const [currentIndex, setCurrentIndex] = useState(0);
        return (
            ...
                <img src={featuredImages[currentIndex]} alt="" />
            ...
)}

Now we need to find out some way to change the value of this state. Like if we press next we want to add 1 or if we press previous we want to subtract 1.

But, the most important point is we don’t want the final value to be more than or equal to the length of our featuredImages array. Also, we don’t want the final result to be less than zero.

To do so we need to apply a little formula. So, let’s create a variable called count outside of the component. Use let not const because we want to muited its value.

1
2
3
4
5
6
7
let count = 0;
export default App() {
...
const handleOnNextClick = () => {
    count = (count + 1) % featuredImages.length;
    setCurrentIndex(count);
  };

Okay after looking at this logic you may feel like, ohhh what is going on here? But this is the easiest way to keep our currentIndex within the range area from 0 to 2.

Because we have an array with 3 items. Which means if you want to access the first item that will be index [0] and If we access that last item then that will be [2]. So if you go beyond 2 that will be undefined.

This logic will work whether you have 3 items inside your array or 30. Ok, enough talking. Now if you test your app and press the next button it will change images in a loop.

1
2
3
4
5
 const handleOnPrevClick = () => {
   const productsLength = featuredImages.length;
   count = (currentIndex + productsLength - 1) % productsLength;
   setCurrentIndex(count);
 };

In the same way here we want to move our slider back when we press the previous button. Again this logic will subtract our count till 0 and then it will come to 2. Remember no -1.

Playing carousel infinitely

Now if you want your slider to be run infinitely. You can simply create a function called startSlider. Where you can add the setInterval method and call handleOnNextClick with the interval you like.

Then just call startSlider function inside the useEffect hook. Like this.

1
2
3
4
5
6
7
8
9
useEffect(() => {
    startSlider();
  }, []);

  const startSlider = () => {
    setInterval(() => {
      handleOnNextClick();
    }, 3000);
  };

Adding animations to our slides

This is the result so far. The only thing that is missing here is animation. Let’s see how we can add that as well.

running carousel on loop

Adding animation to the images which are updating constantly is not an easy task. But anyway it’s the challenge we need to tackle to get the end result, right?

First let’s talk about what the challenges are and how we are going to solve them?

The problem is, if we add any animation to our image it will run only for a first time when our component renders on the screen. But we want to add that animation or transition whatever that is on every image update.

Now before anything else let’s add some simple animation inside our index.css. Now you may wonder. ”Why? We can’t use tailwind css to add animation”.

Hmm, yes but no. Yes we can use tailwind classes to add animation but no, the animation that we want is not available inside tailwind css. So, just copy and paste this css code inside your ./src/index.css.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* ./src/index.css */
.fade-anim img {
  animation: fadeAnim 0.5s ease-out;
}
@keyframes fadeAnim {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* your default tailwind styles will be here */

The animation is pretty straightforward. We are just manipulating the opacity of the image.

Now to get the result we want. We need to add the fade-anim class every time when we change the currentIndex state. We also need to remove it after the animation is end.

For that, first let’s use the useRef hook and create a reference called slideRef. Now we can pass it to the ref prop of the second wrapper div.

1
2
3
4
5
6
7
export default function ReactSlider() {
    const slideRef = useRef();
    return (
        <div className="max-w-screen-xl m-auto">
            <div ref={slideRef} className="w-full relative select-none">
            ...
)}

Now if you add this line of code at the end of handleOnNextClick and handleOnPrevClick.

1
slideRef.current.classList.add("fade-anim");

We are using the reference of that div and adding the class which we created earlier. Now we also need to remove this class fade-anim before we move to the next image. Only then we will get the animation on every image.

For that you can create a method called removeAnimation and do the exact opposite to remove the class we added before.

1
2
3
  const removeAnimation = () => {
    slideRef.current.classList.remove("fade-anim");
  };

Then after the calling of startSlider method add an event listener called animationend to the slideRef and pass removeAnimation as the callback.

1
2
3
4
useEffect(() => {
    startSlider();
    slideRef.current.addEventListener("animationend", removeAnimation);
  }, []);

The result of your hardwork so far.

animated carousel

Pause slide on mouse enter

Ok so far so good. Now we need to add a feature where we want to pause the carousel if we enter the mouse and start it when the mouse leaves.

To do that first we can use the clearInterval method but for that we need to have the id returned from the setInterval method. So, create a variable called slideInterval outside of the component.

Then assign the setInterval method as the value inside startSlider method.

1
2
3
4
5
6
7
8
9
10
11
12
let count = 0;
let slideInterval;

export default function App() {
...
 const startSlider = () => {
    slideInterval = setInterval(() => {
      handleOnNextClick();
    }, 3000);
  };
...
)}

Now let's create a method called pauseSlider where we will clear the interval.

1
2
3
  const pauseSlider = () => {
    clearInterval(slideInterval);
  };

Then we just need to add two event listeners like before we added animationend. This time events will be mouseenter and mouseleave.

If the mouse enters the slideRef area we want to pause the slider and if the mouse goes away we want to start it again. Like this.

1
2
3
4
5
6
7
8
9
10
useEffect(() => {
    startSlider();
    slideRef.current.addEventListener("animationend", removeAnimation);
    slideRef.current.addEventListener("mouseenter", pauseSlider);
    slideRef.current.addEventListener("mouseleave", startSlider);

    return () => {
        clearInterval(slideInterval);
    };
  }, []);

This is how our useEffect hook looks right now. Also there you can see I am returning a function and clearing the interval. This is called cleanup. This function will run on component unmounting stage.

If we didn't stop the slider and move to the next page on our app. We will get an error because in that page we will not have slideRef.current.

Adding icons

With the code above our responsive carousel using React and Tailwind CSS is completely ready. But the possibility for improvisation is endless.

So, let's do one here by adding icons instead of the plain next and previous text. For that we need to install a package called react-icons.

1
npm i react-icons

Then import these two icons from react-icons/ai like below.

1
import { AiOutlineVerticalRight, AiOutlineVerticalLeft } from "react-icons/ai";

Now you can use these two icons inside those buttons instead of plain text. Also you can specify the size as well.

1
2
3
4
5
6
7
8
...
<button onClick={handleOnPrevClick}>
    <AiOutlineVerticalRight size={35} />
</button>
<button onClick={handleOnPrevClick}>
    <AiOutlineVerticalLeft size={35} />
</button>
...

Also if you want some style to your button you can add these class names to the button.

1
<button className="bg-black text-white p-1 rounded-full bg-opacity-50 cursor-pointer hover:bg-opacity-100 transition">

The final result you can check here.