Docs
Testimonial Carousel

Testimonial Carousel

A modern, auto-scrolling testimonial carousel with multiple style variants. Features infinite scroll, pause on hover, and customizable animations.

Loved by thousands of users

See what our customers have to say about their experience

Default Style

Large cards with prominent user info

S
Sarah Johnson
CEO
TechCorp

"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"

M
Michael Chen
Product Manager
InnovateLabs

"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."

E
Emily Rodriguez
Design Lead
CreativeStudio

"The attention to detail is incredible. It's rare to find a product that's both powerful and easy to use. Our entire team loves it."

D
David Kim
CTO
DataFlow

"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."

J
Jessica Taylor
Marketing Director
GrowthHub

"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."

A
Alex Martinez
Founder
StartupXYZ

"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"

S
Sarah Johnson
CEO
TechCorp

"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"

M
Michael Chen
Product Manager
InnovateLabs

"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."

E
Emily Rodriguez
Design Lead
CreativeStudio

"The attention to detail is incredible. It's rare to find a product that's both powerful and easy to use. Our entire team loves it."

D
David Kim
CTO
DataFlow

"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."

J
Jessica Taylor
Marketing Director
GrowthHub

"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."

A
Alex Martinez
Founder
StartupXYZ

"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"

S
Sarah Johnson
CEO
TechCorp

"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"

M
Michael Chen
Product Manager
InnovateLabs

"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."

E
Emily Rodriguez
Design Lead
CreativeStudio

"The attention to detail is incredible. It's rare to find a product that's both powerful and easy to use. Our entire team loves it."

D
David Kim
CTO
DataFlow

"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."

J
Jessica Taylor
Marketing Director
GrowthHub

"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."

A
Alex Martinez
Founder
StartupXYZ

"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"

Card Style

Elegant cards with quote icon

"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"

S
Sarah Johnson
CEO
TechCorp

"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."

M
Michael Chen
Product Manager
InnovateLabs

"The attention to detail is incredible. It's rare to find a product that's both powerful and easy to use. Our entire team loves it."

E
Emily Rodriguez
Design Lead
CreativeStudio

"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."

D
David Kim
CTO
DataFlow

"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."

J
Jessica Taylor
Marketing Director
GrowthHub

"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"

A
Alex Martinez
Founder
StartupXYZ

"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"

S
Sarah Johnson
CEO
TechCorp

"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."

M
Michael Chen
Product Manager
InnovateLabs

"The attention to detail is incredible. It's rare to find a product that's both powerful and easy to use. Our entire team loves it."

E
Emily Rodriguez
Design Lead
CreativeStudio

"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."

D
David Kim
CTO
DataFlow

"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."

J
Jessica Taylor
Marketing Director
GrowthHub

"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"

A
Alex Martinez
Founder
StartupXYZ

"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"

S
Sarah Johnson
CEO
TechCorp

"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."

M
Michael Chen
Product Manager
InnovateLabs

"The attention to detail is incredible. It's rare to find a product that's both powerful and easy to use. Our entire team loves it."

E
Emily Rodriguez
Design Lead
CreativeStudio

"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."

D
David Kim
CTO
DataFlow

"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."

J
Jessica Taylor
Marketing Director
GrowthHub

"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"

A
Alex Martinez
Founder
StartupXYZ

Compact Style

Space-efficient design for more content

This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!

S
Sarah Johnson
CEO at TechCorp

Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate.

M
Michael Chen
Product Manager at InnovateLabs

The attention to detail is incredible. It's rare to find a product that's both powerful and easy to use. Our entire team loves it.

E
Emily Rodriguez
Design Lead at CreativeStudio

Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business.

D
David Kim
CTO at DataFlow

Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant.

J
Jessica Taylor
Marketing Director at GrowthHub

As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!

A
Alex Martinez
Founder at StartupXYZ

This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!

S
Sarah Johnson
CEO at TechCorp

Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate.

M
Michael Chen
Product Manager at InnovateLabs

The attention to detail is incredible. It's rare to find a product that's both powerful and easy to use. Our entire team loves it.

E
Emily Rodriguez
Design Lead at CreativeStudio

Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business.

D
David Kim
CTO at DataFlow

Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant.

J
Jessica Taylor
Marketing Director at GrowthHub

As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!

A
Alex Martinez
Founder at StartupXYZ

This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!

S
Sarah Johnson
CEO at TechCorp

Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate.

M
Michael Chen
Product Manager at InnovateLabs

The attention to detail is incredible. It's rare to find a product that's both powerful and easy to use. Our entire team loves it.

E
Emily Rodriguez
Design Lead at CreativeStudio

Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business.

D
David Kim
CTO at DataFlow

Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant.

J
Jessica Taylor
Marketing Director at GrowthHub

As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!

A
Alex Martinez
Founder at StartupXYZ

Features

  • Infinite scroll - Seamless continuous animation
  • Multiple variants - Default, card, and compact styles
  • Pause on hover - Interactive user control
  • Star ratings - Visual rating display
  • Bidirectional - Scroll left or right
  • Avatar support - Images or gradient initials
  • Smooth animations - Powered by Framer Motion
  • Responsive - Works on all screen sizes
  • Customizable - Speed, direction, and styling
  • TypeScript support - Full type safety

Installation

Install dependencies

npm install lucide-react

Copy and paste the following code into your project.

import { InfiniteScroll } from "@/components/ui/infinite-scroll";
import { Star, Quote } from "lucide-react";
 
interface Testimonial {
  id: string;
  name: string;
  role: string;
  company: string;
  content: string;
  rating: number;
  image?: string;
}
 
interface TestimonialCarouselProps {
  testimonials: Testimonial[];
  direction?: "normal" | "reverse";
  duration?: number;
  pauseOnHover?: boolean;
  showFade?: boolean;
  variant?: "default" | "compact" | "card";
}
 
export function TestimonialCarousel({
  testimonials,
  direction = "normal",
  duration = 30000,
  pauseOnHover = true,
  showFade = true,
  variant = "default",
}: TestimonialCarouselProps) {
  if (variant === "compact") {
    return (
      <InfiniteScroll
        direction={direction}
        duration={duration}
        pauseOnHover={pauseOnHover}
        showFade={showFade}
        className="py-8"
      >
        {testimonials.map((testimonial) => (
          <div
            key={testimonial.id}
            className="mx-3 w-[400px] flex-shrink-0 rounded-xl border border-zinc-800 bg-zinc-900/50 p-6 backdrop-blur-sm"
          >
            <div className="mb-4 flex items-center gap-1">
              {Array.from({ length: 5 }).map((_, i) => (
                <Star
                  key={i}
                  className={`h-4 w-4 ${
                    i < testimonial.rating
                      ? "fill-yellow-500 text-yellow-500"
                      : "fill-zinc-700 text-zinc-700"
                  }`}
                />
              ))}
            </div>
            <p className="mb-4 text-sm leading-relaxed text-zinc-300">
              {testimonial.content}
            </p>
            <div className="flex items-center gap-3">
              {testimonial.image ? (
                <img
                  src={testimonial.image}
                  alt={testimonial.name}
                  className="h-10 w-10 rounded-full object-cover"
                />
              ) : (
                <div className="flex h-10 w-10 items-center justify-center rounded-full bg-orange-600 text-sm font-semibold text-white">
                  {testimonial.name.charAt(0)}
                </div>
              )}
              <div>
                <div className="text-sm font-semibold text-white">
                  {testimonial.name}
                </div>
                <div className="text-xs text-zinc-500">
                  {testimonial.role} at {testimonial.company}
                </div>
              </div>
            </div>
          </div>
        ))}
      </InfiniteScroll>
    );
  }
 
  if (variant === "card") {
    return (
      <InfiniteScroll
        direction={direction}
        duration={duration}
        pauseOnHover={pauseOnHover}
        showFade={showFade}
        className="py-8"
      >
        {testimonials.map((testimonial) => (
          <div
            key={testimonial.id}
            className="group mx-3 w-[450px] flex-shrink-0 rounded-2xl border border-zinc-800 bg-zinc-900 p-8 shadow-xl transition-all hover:border-zinc-700 hover:shadow-2xl"
          >
            <Quote className="mb-4 h-8 w-8 text-orange-500/30" />
            <p className="mb-6 text-base leading-relaxed text-zinc-200">
              "{testimonial.content}"
            </p>
            <div className="flex items-center justify-between">
              <div className="flex items-center gap-4">
                {testimonial.image ? (
                  <img
                    src={testimonial.image}
                    alt={testimonial.name}
                    className="h-12 w-12 rounded-full object-cover ring-2 ring-zinc-800"
                  />
                ) : (
                  <div className="flex h-12 w-12 items-center justify-center rounded-full bg-orange-600 text-base font-bold text-white ring-2 ring-zinc-800">
                    {testimonial.name.charAt(0)}
                  </div>
                )}
                <div>
                  <div className="font-semibold text-white">
                    {testimonial.name}
                  </div>
                  <div className="text-sm text-zinc-400">
                    {testimonial.role}
                  </div>
                  <div className="text-xs text-zinc-500">
                    {testimonial.company}
                  </div>
                </div>
              </div>
              <div className="flex items-center gap-1">
                {Array.from({ length: 5 }).map((_, i) => (
                  <Star
                    key={i}
                    className={`h-4 w-4 ${
                      i < testimonial.rating
                        ? "fill-yellow-500 text-yellow-500"
                        : "fill-zinc-800 text-zinc-800"
                    }`}
                  />
                ))}
              </div>
            </div>
          </div>
        ))}
      </InfiniteScroll>
    );
  }
 
  // Default variant
  return (
    <InfiniteScroll
      direction={direction}
      duration={duration}
      pauseOnHover={pauseOnHover}
      showFade={showFade}
      className="py-8"
    >
      {testimonials.map((testimonial) => (
        <div
          key={testimonial.id}
          className="mx-4 w-[500px] flex-shrink-0 rounded-2xl border border-zinc-800 bg-zinc-950 p-8 shadow-2xl"
        >
          <div className="mb-6 flex items-start justify-between">
            <div className="flex items-center gap-4">
              {testimonial.image ? (
                <img
                  src={testimonial.image}
                  alt={testimonial.name}
                  className="h-14 w-14 rounded-full object-cover"
                />
              ) : (
                <div className="flex h-14 w-14 items-center justify-center rounded-full bg-orange-600 text-lg font-bold text-white">
                  {testimonial.name.charAt(0)}
                </div>
              )}
              <div>
                <div className="text-lg font-bold text-white">
                  {testimonial.name}
                </div>
                <div className="text-sm text-zinc-400">{testimonial.role}</div>
                <div className="text-xs text-zinc-500">
                  {testimonial.company}
                </div>
              </div>
            </div>
            <div className="flex items-center gap-1">
              {Array.from({ length: 5 }).map((_, i) => (
                <Star
                  key={i}
                  className={`h-5 w-5 ${
                    i < testimonial.rating
                      ? "fill-yellow-500 text-yellow-500"
                      : "fill-zinc-800 text-zinc-800"
                  }`}
                />
              ))}
            </div>
          </div>
          <p className="text-base leading-relaxed text-zinc-300">
            "{testimonial.content}"
          </p>
        </div>
      ))}
    </InfiniteScroll>
  );
}

Also copy and paste this infinite-scroll component into the /components/ui folder.

"use client";
 
import React, { useRef, useEffect, useState } from "react";
import { motion, useAnimationControls } from "framer-motion";
import { cn } from "@/lib/utils";
 
interface InfiniteScrollProps {
  /** Additional CSS classes to apply to the container */
  className?: string;
  /** Duration of the scroll animation in milliseconds. Default is 15000 */
  duration?: number;
  /** Direction of the scroll animation. Can be "normal" (left to right) or "reverse" (right to left) */
  direction?: "normal" | "reverse";
  /** Background color for the fade effect container. Default is "#ffffff" */
  containerColor?: string;
  /** Whether to show the fade effect at the edges. Default is true */
  showFade?: boolean;
  /** Content to be scrolled infinitely */
  children: React.ReactNode;
  /** Whether to pause the animation when hovering over the content. Default is true */
  pauseOnHover?: boolean;
}
 
export function InfiniteScroll({
  className,
  duration = 15000,
  direction = "normal",
  containerColor = "#ffffff",
  showFade = true,
  children,
  pauseOnHover = true,
}: InfiniteScrollProps) {
  const [contentWidth, setContentWidth] = useState<number>(0);
  const [isPaused, setIsPaused] = useState(false);
  const scrollerRef = useRef<HTMLDivElement>(null);
  const contentRef = useRef<HTMLDivElement>(null);
  const controls = useAnimationControls();
  const elapsedTimeRef = useRef(0);
  const lastTimeRef = useRef(0);
 
  useEffect(() => {
    const content = contentRef.current;
    if (!content) return;
 
    const updateWidth = () => {
      const width = content.offsetWidth;
      setContentWidth(width);
    };
 
    updateWidth();
    window.addEventListener("resize", updateWidth);
    return () => window.removeEventListener("resize", updateWidth);
  }, [children]);
 
  useEffect(() => {
    if (!contentWidth) return;
 
    const startX = direction === "normal" ? 0 : -contentWidth;
    const endX = direction === "normal" ? -contentWidth : 0;
 
    if (!isPaused) {
      const remainingDuration = duration - elapsedTimeRef.current;
      const progress = elapsedTimeRef.current / duration;
      const currentX =
        direction === "normal"
          ? startX + (endX - startX) * progress
          : endX + (startX - endX) * (1 - progress);
 
      controls.set({ x: currentX });
      controls.start({
        x: endX,
        transition: {
          duration: remainingDuration / 1000,
          ease: "linear",
          repeat: Infinity,
          repeatType: "loop",
          repeatDelay: 0,
        },
      });
 
      lastTimeRef.current = Date.now();
    }
  }, [controls, direction, duration, contentWidth, isPaused]);
 
  const handleMouseEnter = () => {
    if (!pauseOnHover) return;
 
    const currentTime = Date.now();
    const deltaTime = currentTime - lastTimeRef.current;
    elapsedTimeRef.current = (elapsedTimeRef.current + deltaTime) % duration;
 
    setIsPaused(true);
    controls.stop();
  };
 
  const handleMouseLeave = () => {
    if (!pauseOnHover) return;
    lastTimeRef.current = Date.now();
    setIsPaused(false);
  };
 
  return (
    <div
      className={cn(
        "relative flex shrink-0 flex-col gap-4 overflow-hidden py-3",
        className,
      )}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      <div className="flex">
        <motion.div
          ref={scrollerRef}
          className="flex shrink-0"
          animate={controls}
        >
          <div ref={contentRef} className="flex shrink-0">
            {children}
          </div>
          <div className="flex shrink-0">{children}</div>
          <div className="flex shrink-0">{children}</div>
        </motion.div>
      </div>
      {showFade && (
        <div
          className="pointer-events-none absolute inset-0 bg-linear-to-r from-background via-transparent to-background"
          style={{ "--container-color": containerColor } as React.CSSProperties}
        />
      )}
    </div>
  );
}

Usage

Basic Usage

import TestimonialCarousel from "@/components/ui/testimonial-carousel";
 
const testimonials = [
  {
    id: "1",
    name: "Sarah Johnson",
    role: "CEO",
    company: "TechCorp",
    content: "This product has completely transformed how we work.",
    rating: 5,
  },
  // ... more testimonials
];
 
export default function Page() {
  return (
    <div className="py-20">
      <TestimonialCarousel testimonials={testimonials} />
    </div>
  );
}

Card Variant

<TestimonialCarousel
  testimonials={testimonials}
  variant="card"
  direction="reverse"
  duration={35000}
/>

Compact Variant

<TestimonialCarousel
  testimonials={testimonials}
  variant="compact"
  duration={25000}
/>

With Custom Images

const testimonials = [
  {
    id: "1",
    name: "Sarah Johnson",
    role: "CEO",
    company: "TechCorp",
    content: "Amazing product!",
    rating: 5,
    image: "/avatars/sarah.jpg", // Add custom image
  },
];
 
<TestimonialCarousel testimonials={testimonials} />

Reverse Direction

<TestimonialCarousel
  testimonials={testimonials}
  direction="reverse"
  duration={40000}
/>

Without Pause on Hover

<TestimonialCarousel
  testimonials={testimonials}
  pauseOnHover={false}
/>

Props

TestimonialCarouselProps

PropTypeDefaultDescription
testimonialsTestimonial[]RequiredArray of testimonial objects
variant"default" | "compact" | "card""default"Visual style variant
direction"normal" | "reverse""normal"Scroll direction
durationnumber30000Animation duration in ms
pauseOnHoverbooleantruePause animation on hover
showFadebooleantrueShow fade effect at edges

Testimonial

PropTypeRequiredDescription
idstringYesUnique identifier
namestringYesPerson's name
rolestringYesJob title
companystringYesCompany name
contentstringYesTestimonial text
ratingnumberYesStar rating (1-5)
imagestringNoAvatar image URL

Variants

Default

  • Large cards (500px width)
  • Prominent user info at top
  • Rating stars in top-right
  • Best for detailed testimonials

Card

  • Medium cards (450px width)
  • Quote icon accent
  • Elegant gradient background
  • Hover effects
  • Best for professional look

Compact

  • Smaller cards (400px width)
  • Space-efficient layout
  • Rating stars at top
  • Best for showing more content

TypeScript Interface

interface Testimonial {
  id: string;
  name: string;
  role: string;
  company: string;
  content: string;
  rating: number;
  image?: string;
}
 
interface TestimonialCarouselProps {
  testimonials: Testimonial[];
  direction?: "normal" | "reverse";
  duration?: number;
  pauseOnHover?: boolean;
  showFade?: boolean;
  variant?: "default" | "compact" | "card";
}

Use Cases

Perfect for:

  • Landing pages
  • Product showcases
  • SaaS websites
  • Portfolio sites
  • Marketing pages
  • Social proof sections
  • Customer success stories
  • Review displays