DEV Community

Cover image for 🧠 How to Build a Carousel in ReactJS with Tailwind
Manish Kumar
Manish Kumar

Posted on

🧠 How to Build a Carousel in ReactJS with Tailwind

Everyone adds carousels to their websites. Most of the time, people simply install a library like Slick Carousel, add a few props, and move on. However, that approach often adds unnecessary code. In this guide, we’ll learn how to build a clean, lightweight carousel from scratch using React and Tailwind CSS.

🚀 Step 1: Create a React + TypeScript project with Vite

npm create vite@latest my-carousel --template react-ts
cd my-carousel
Enter fullscreen mode Exit fullscreen mode

If you want plain JavaScript, replace react-ts with react.


🎨 Step 2: Install Tailwind CSS

To install Tailwind CSS, follow the instructions at the link


🖼 Step 3: Load Your Images

Dump your images into public/carouselImages/. Now set them up:

const carouselImages = [
  "/carouselImages/carousel-1.jpg",
  "/carouselImages/carousel-2.jpg",
  "/carouselImages/carousel-3.jpg",
];
Enter fullscreen mode Exit fullscreen mode

⚛️ Step 4: The React Component

import { useState, useEffect } from "react";

const carouselImages = [
  "/carouselImages/carousel-1.jpg",
  "/carouselImages/carousel-2.jpg",
  "/carouselImages/carousel-3.jpg",
];

export const Carousel = () => {
  const [current, setCurrent] = useState(0);

  const prevSlide = () =>
    setCurrent((prev) => (prev === 0 ? carouselImages.length - 1 : prev - 1));

  const nextSlide = () =>
    setCurrent((prev) => (prev === carouselImages.length - 1 ? 0 : prev + 1));

  const goToSlide = (index: number) => setCurrent(index);

  useEffect(() => {
    const interval = setInterval(nextSlide, 5000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div className="relative w-full aspect-[16/6] overflow-hidden">
      {carouselImages.map((image, index) => (
        <div
          key={index}
          className={`absolute inset-0 transition-opacity duration-700 ease-in-out ${
            index === current ? "opacity-100 z-10" : "opacity-0 z-0"
          }`}
        >
          <img
            src={image}
            alt={`Slide ${index + 1}`}
            className="w-full h-full object-cover"
          />
        </div>
      ))}

      {/* Controls */}
      <button onClick={prevSlide} className="absolute top-1/2 left-4 -translate-y-1/2 bg-black/50 text-white p-2 rounded-full z-20">
        ‹
      </button>
      <button onClick={nextSlide} className="absolute top-1/2 right-4 -translate-y-1/2 bg-black/50 text-white p-2 rounded-full z-20">
        ›
      </button>

      {/* Indicators */}
      <div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
        {carouselImages.map((_, index) => (
          <button
            key={index}
            onClick={() => goToSlide(index)}
            className={`w-3 h-3 rounded-full border border-white ${
              index === current ? "bg-white" : "bg-white/40"
            }`}
          />
        ))}
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

🧠 How Does It Work?

Let’s deconstruct this:

🔁 prevSlide

setCurrent((prev) => (prev === 0 ? carouselImages.length - 1 : prev - 1));
Enter fullscreen mode Exit fullscreen mode

If you’re on the first slide, wrap to the last. Else, go to the previous. Simple logic, clean loop.

🔂 nextSlide

setCurrent((prev) => (prev === carouselImages.length - 1 ? 0 : prev + 1));
Enter fullscreen mode Exit fullscreen mode

If you’re on the last slide, wrap to the first. Otherwise, move forward. This is used both on the "›" button and the auto-play logic.

🎯 goToSlide

(index: number) => setCurrent(index);
Enter fullscreen mode Exit fullscreen mode

This allows direct jumping to a slide. Used in the dot indicators at the bottom.


🖼 How Slides Are Actually Rendered

This is where the clean animation happens:

<div
  key={index}
  className={`absolute inset-0 transition-opacity duration-700 ease-in-out ${
    index === current ? "opacity-100 z-10" : "opacity-0 z-0"
  }`}
>
Enter fullscreen mode Exit fullscreen mode

All slides are stacked on top of each other.

  • Only the current index gets opacity-100 and z-10
  • Others get opacity-0 and z-0 (hidden behind)

Instead of unmounting and remounting, you simply fade between preloaded images.


🕒 Auto-Play

useEffect(() => {
  const interval = setInterval(nextSlide, 5000);
  return () => clearInterval(interval);
}, []);
Enter fullscreen mode Exit fullscreen mode

Every 5 seconds, nextSlide() is triggered.


💬 Final Thoughts

Creating a carousel with React and Tailwind helps you understand the basics—state management, rendering, and styling.

Top comments (0)