Build Testimonial Carousel in React with Dynamic Animations

In modern web design, testimonials are a powerful tool for building trust and showcasing positive feedback. An interactive testimonial carousel enhances user engagement, making it easy for visitors to browse through reviews without overwhelming the page layout. In this tutorial, we’ll create a feature-rich testimonial carousel component using React that supports smooth animations, automatic slide transitions, and responsive design.

Features of Our Testimonial Carousel

  1. User-Friendly Navigation: Easily navigate between testimonials with left and right arrows.
  2. Auto-Advancing Slides: Slides automatically transition to keep the carousel active and engaging.
  3. Responsive Design: Adjusts layout for various screen sizes.
  4. Dynamic Animation: Smooth transitions improve user experience and visual appeal.

Let's get started with the code and discuss the implementation step-by-step.

Prerequisites

  • React: Basic knowledge of React components, state, and hooks.
  • Lucide Icons: For easy integration of icons such as ChevronLeft, ChevronRight, and Star.

Building the Testimonial Carousel

1. Setting Up Data: Define an array of testimonial objects with fields like name, role, company, content, and rating. 

const testimonials = [
  {
    id: 1,
    name: "Sarah Johnson",
    role: "Marketing Director",
    image: "/api/placeholder/100/100",
    content: "This product has completely transformed our marketing campaigns.",
    rating: 5,
    company: "TechCorp Inc.",
  },
  // Add more testimonials
];

2. Component Structure: Create a TestimonialCarousel component that will render the active testimonial, handle navigation, and manage slide transitions.

3. State Management

  • activeIndex: Tracks the current testimonial being displayed.
  • isAnimating: Controls transition animations, preventing fast clicks from disrupting smooth transitions.
  • direction: Determines slide transition direction for left or right navigation.

4. Navigating Slides: Define nextSlide and prevSlide functions to update the activeIndex and isAnimating state.

const nextSlide = () => {
  if (!isAnimating) {
    setDirection('left');
    setIsAnimating(true);
    setActiveIndex((prev) => (prev + 1) % testimonials.length);
  }
};

const prevSlide = () => {
  if (!isAnimating) {
    setDirection('right');
    setIsAnimating(true);
    setActiveIndex((prev) => (prev - 1 + testimonials.length) % testimonials.length);
  }
};

5. Automatic Slide Transition: UseEffect to set up a timer that auto-advances slides every 5 seconds, providing a continuous rotation through testimonials.

6. Rendering Stars: For each testimonial, render a star rating using Lucide’s Star component, with filled stars representing the rating.

const renderStars = (rating) => {
  return [...Array(5)].map((_, index) => (
    <Star
      key={index}
      size={16}
      className={index < rating ? "fill-yellow-400 text-yellow-400" : "text-gray-300"}
    />
  ));
};

7. Conditional Animations: For smooth transitions, apply conditional classes based on direction and isAnimating to achieve left or right slide animations.

Full Code

import React, { useState, useEffect } from 'react';
import { ChevronLeft, ChevronRight, Star, Quote } from 'lucide-react';

const testimonials = [
  {
    id: 1,
    name: "Sarah Johnson",
    role: "Marketing Director",
    image: "/api/placeholder/100/100",
    content:
      "This product has completely transformed how we handle our marketing campaigns. The interface is intuitive and the results are outstanding.",
    rating: 5,
    company: "TechCorp Inc.",
  },
  {
    id: 2,
    name: "Michael Chen",
    role: "Software Engineer",
    image: "/api/placeholder/100/100",
    content:
      "The developer experience is fantastic. API integration was seamless and the documentation is comprehensive. Highly recommended!",
    rating: 5,
    company: "DevStack Solutions",
  },
  {
    id: 3,
    name: "Emma Williams",
    role: "Product Manager",
    image: "/api/placeholder/100/100",
    content:
      "We've seen a 40% increase in team productivity since implementing this solution. The automated workflows are a game-changer.",
    rating: 4,
    company: "Innovate Labs",
  },
  {
    id: 4,
    name: "David Rodriguez",
    role: "CEO",
    image: "/api/placeholder/100/100",
    content:
      "The best investment we've made this year. Customer support is exceptional and the platform keeps getting better with each update.",
    rating: 5,
    company: "StartUp Ventures",
  },
];

const TestimonialCarousel = () => {
  const [activeIndex, setActiveIndex] = useState(0);
  const [isAnimating, setIsAnimating] = useState(false);
  const [direction, setDirection] = useState('');

  const nextSlide = () => {
    if (!isAnimating) {
      setDirection('left');
      setIsAnimating(true);
      setActiveIndex((prev) => (prev + 1) % testimonials.length);
    }
  };

  const prevSlide = () => {
    if (!isAnimating) {
      setDirection('right');
      setIsAnimating(true);
      setActiveIndex((prev) => (prev - 1 + testimonials.length) % testimonials.length);
    }
  };

  useEffect(() => {
    if (isAnimating) {
      const timer = setTimeout(() => {
        setIsAnimating(false);
      }, 500);
      return () => clearTimeout(timer);
    }
  }, [isAnimating]);

  // Auto-advance slides
  useEffect(() => {
    const timer = setInterval(nextSlide, 5000);
    return () => clearInterval(timer);
  }, []);

  const renderStars = (rating) => {
    return [...Array(5)].map((_, index) => (
      <Star
        key={index}
        size={16}
        className={index < rating ? 'fill-yellow-400 text-yellow-400' : 'text-gray-300'}
      />
    ));
  };

  return (
    <div className="w-full bg-gray-50 py-12 px-4">
      <div className="max-w-6xl mx-auto">
        {/* Header */}
        <div className="text-center mb-12">
          <h2 className="text-3xl font-bold text-gray-900 mb-4">What Our Clients Say</h2>
          <p className="text-gray-600 max-w-2xl mx-auto">
            Discover why thousands of companies trust us with their business needs
          </p>
        </div>

        {/* Carousel */}
        <div className="relative">
          {/* Main carousel container */}
          <div className="overflow-hidden relative">
            <div
              className={`flex transition-transform duration-500 ease-in-out ${
                isAnimating ? (direction === 'left' ? '-translate-x-full' : 'translate-x-full') : 'translate-x-0'
              }`}
            >
              {/* Current testimonial */}
              <div className="w-full flex-shrink-0">
                <div className="grid md:grid-cols-2 gap-8">
                  {/* Testimonial card */}
                  <div className="bg-white rounded-lg shadow-lg p-8 relative">
                    <Quote className="absolute top-4 right-4 text-blue-100" size={48} />
                    <div className="flex items-center mb-6">
                      <img
                        src={testimonials[activeIndex].image}
                        alt={testimonials[activeIndex].name}
                        className="w-16 h-16 rounded-full object-cover mr-4"
                      />
                      <div>
                        <h3 className="font-semibold text-lg">{testimonials[activeIndex].name}</h3>
                        <p className="text-gray-600 text-sm">{testimonials[activeIndex].role}</p>
                        <p className="text-blue-600 text-sm">{testimonials[activeIndex].company}</p>
                      </div>
                    </div>
                    <p className="text-gray-700 mb-4">"{testimonials[activeIndex].content}"</p>
                    <div className="flex items-center">{renderStars(testimonials[activeIndex].rating)}</div>
                  </div>

                  <div className="hidden md:block">
                    <div className="bg-white rounded-lg shadow-lg p-8 opacity-50">
                      <div className="flex items-center mb-6">
                        <img
                          src={testimonials[(activeIndex + 1) % testimonials.length].image}
                          alt={testimonials[(activeIndex + 1) % testimonials.length].name}
                          className="w-16 h-16 rounded-full object-cover mr-4"
                        />
                        <div>
                          <h3 className="font-semibold text-lg">
                            {testimonials[(activeIndex + 1) % testimonials.length].name}
                          </h3>
                          <p className="text-gray-600 text-sm">
                            {testimonials[(activeIndex + 1) % testimonials.length].role}
                          </p>
                        </div>
                      </div>
                      <p className="text-gray-700 mb-4">
                        "{testimonials[(activeIndex + 1) % testimonials.length].content}"
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>

          <button
            onClick={prevSlide}
            className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 bg-white rounded-full p-2 shadow-lg hover:bg-gray-50 transition-colors"
          >
            <ChevronLeft size={24} />
          </button>
          <button
            onClick={nextSlide}
            className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 bg-white rounded-full p-2 shadow-lg hover:bg-gray-50 transition-colors"
          >
            <ChevronRight size={24} />
          </button>

          <div className="flex justify-center mt-8 gap-2">
            {testimonials.map((_, index) => (
              <button
                key={index}
                className={`w-2 h-2 rounded-full transition-all duration-300 ${
                  index === activeIndex ? 'bg-blue-600 w-4' : 'bg-gray-300'
                }`}
                onClick={() => setActiveIndex(index)}
              />
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};
export default TestimonialCarousel;

Product manager

Customer

A testimonial carousel is a great way to showcase feedback in a compact, interactive format. Using React's state and lifecycle hooks along with conditional animations, you can create an engaging user experience that effectively communicates positive client feedback. The flexibility to add additional testimonials, customize slide intervals, or even use it in different sections of your website is easy, thanks to the component’s modularity.