In this tutorial, we will see how to add a Tailwind CSS carousel in React without using any library.
React with Tailwind CSS Carousel Slider
Modified carousel section: added overflow-hidden class to prevent image overflow, wrapped items in carousel-inner div with flex layout, applied transition-transform and duration-500 classes for smooth transitions, calculated transform property to translate items horizontally, added flex-shrink-0 and w-full classes to prevent shrinking and ensure full width, used “picsum.photos” for dummy images.
import { useState } from "react";
const Carousel = ({ images }) => {
const [currentIndex, setCurrentIndex] = useState(0);
const handlePrevClick = () => {
setCurrentIndex((currentIndex - 1 + images.length) % images.length);
};
const handleNextClick = () => {
setCurrentIndex((currentIndex + 1) % images.length);
};
return (
<div className="relative">
<div className="carousel overflow-hidden">
<div
className="carousel-inner flex transition-transform duration-500"
style={{ transform: `translateX(-${currentIndex * 100}%)` }}
>
{images.map((image, index) => (
<div key={index} className="carousel-item flex-shrink-0 w-full">
<img src={image.src} alt={image.alt} className="w-full h-auto" />
</div>
))}
</div>
</div>
<button
className="absolute left-0 top-1/2 -translate-y-1/2 bg-gray-300 rounded-full p-2 hover:bg-gray-400 transition-colors duration-300"
onClick={handlePrevClick}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</button>
<button
className="absolute right-0 top-1/2 -translate-y-1/2 bg-gray-300 rounded-full p-2 hover:bg-gray-400 transition-colors duration-300"
onClick={handleNextClick}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</button>
</div>
);
};
const App = () => {
const images = [
{ src: "https://picsum.photos/800/400?random=1", alt: "Image 1" },
{ src: "https://picsum.photos/800/400?random=2", alt: "Image 2" },
{ src: "https://picsum.photos/800/400?random=3", alt: "Image 3" },
];
return (
<div className="container mx-auto">
<h1 className="text-3xl font-bold mb-4">My Carousel</h1>
<Carousel images={images} />
</div>
);
};
export default App;
React Carousel Slider (Tailwind CSS) – TypeScript
import React, { useState } from 'react';
interface Image {
src: string;
alt: string;
}
interface CarouselProps {
images: Image[];
}
const Carousel: React.FC<CarouselProps> = ({ images }) => {
const [currentIndex, setCurrentIndex] = useState<number>(0);
const handlePrevClick = () => {
setCurrentIndex((currentIndex - 1 + images.length) % images.length);
};
const handleNextClick = () => {
setCurrentIndex((currentIndex + 1) % images.length);
};
return (
<div className="relative">
<div className="carousel overflow-hidden">
<div
className="carousel-inner flex transition-transform duration-500"
style={{ transform: `translateX(-${currentIndex * 100}%)` }}
>
{images.map((image, index) => (
<div key={index} className="carousel-item flex-shrink-0 w-full">
<img src={image.src} alt={image.alt} className="w-full h-auto" />
</div>
))}
</div>
</div>
<button
className="absolute left-0 top-1/2 -translate-y-1/2 bg-gray-300 rounded-full p-2 hover:bg-gray-400 transition-colors duration-300"
onClick={handlePrevClick}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</button>
<button
className="absolute right-0 top-1/2 -translate-y-1/2 bg-gray-300 rounded-full p-2 hover:bg-gray-400 transition-colors duration-300"
onClick={handleNextClick}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</button>
</div>
);
};
const App: React.FC = () => {
const images: Image[] = [
{ src: 'https://picsum.photos/800/400?random=1', alt: 'Image 1' },
{ src: 'https://picsum.photos/800/400?random=2', alt: 'Image 2' },
{ src: 'https://picsum.photos/800/400?random=3', alt: 'Image 3' },
];
return (
<div className="container mx-auto">
<h1 className="text-3xl font-bold mb-4">My Carousel</h1>
<Carousel images={images} />
</div>
);
};
export default App;
React Tailwind Carousel with Autoplay and Pagination
This carousel has autoplay, pagination dots with a handleDotClick function to update the currentIndex, and the active dot is highlighted with a different background color.
import { useState, useEffect } from "react";
const Carousel = ({ images, autoplayInterval = 3000 }) => {
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
const autoplayTimer = setInterval(() => {
handleNextClick();
}, autoplayInterval);
return () => clearInterval(autoplayTimer);
}, [currentIndex, autoplayInterval]);
const handlePrevClick = () => {
setCurrentIndex((currentIndex - 1 + images.length) % images.length);
};
const handleNextClick = () => {
setCurrentIndex((currentIndex + 1) % images.length);
};
const handleDotClick = (index) => {
setCurrentIndex(index);
};
return (
<div className="relative">
<div className="carousel overflow-hidden">
<div
className="carousel-inner flex transition-transform duration-500"
style={{ transform: `translateX(-${currentIndex * 100}%)` }}
>
{images.map((image, index) => (
<div key={index} className="carousel-item flex-shrink-0 w-full">
<img src={image.src} alt={image.alt} className="w-full h-auto" />
</div>
))}
</div>
</div>
<button
className="absolute left-0 top-1/2 -translate-y-1/2 bg-gray-300 rounded-full p-2 hover:bg-gray-400 transition-colors duration-300"
onClick={handlePrevClick}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</button>
<button
className="absolute right-0 top-1/2 -translate-y-1/2 bg-gray-300 rounded-full p-2 hover:bg-gray-400 transition-colors duration-300"
onClick={handleNextClick}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</button>
<div className="carousel-dots flex justify-center mt-4">
{images.map((_, index) => (
<button
key={index}
className={`carousel-dot mx-1 w-3 h-3 rounded-full ${index === currentIndex ? "bg-gray-800" : "bg-gray-400"
}`}
onClick={() => handleDotClick(index)}
/>
))}
</div>
</div>
);
};
const App = () => {
const images = [
{ src: "https://picsum.photos/800/400?random=1", alt: "Image 1" },
{ src: "https://picsum.photos/800/400?random=2", alt: "Image 2" },
{ src: "https://picsum.photos/800/400?random=3", alt: "Image 3" },
];
return (
<div className="container mx-auto">
<h1 className="text-3xl font-bold mb-4">
{" "}
Carousel Autoplay and Pagination
</h1>
<Carousel images={images} autoplayInterval={5000} />
</div>
);
};
export default App;
See Also
How to Add Drag-and-Drop Image Upload with Dropzone in React Using Tailwind CSS