Loved by thousands of users
See what our customers have to say about their experience
Default Style
Large cards with prominent user info
"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"
"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."
"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."
"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."
"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."
"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"
"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"
"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."
"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."
"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."
"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."
"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"
"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"
"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."
"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."
"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."
"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."
"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!"
"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."
"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."
"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."
"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."
"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"
"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"
"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."
"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."
"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."
"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."
"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"
"This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!"
"Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate."
"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."
"Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business."
"Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant."
"As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!"
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!
Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate.
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.
Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business.
Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant.
As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!
This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!
Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate.
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.
Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business.
Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant.
As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!
This product has completely transformed how we work. The team is more productive, and our clients are happier than ever. Highly recommend!
Outstanding quality and support. We've seen a 40% increase in efficiency since implementing this solution. The ROI was immediate.
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.
Best investment we've made this year. The scalability and performance are exactly what we needed for our growing business.
Game-changer for our marketing team. We've doubled our output while maintaining quality. The automation features are brilliant.
As a startup, we needed something reliable and affordable. This exceeded our expectations on both fronts. Couldn't be happier!
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-reactCopy 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
| Prop | Type | Default | Description |
|---|---|---|---|
testimonials | Testimonial[] | Required | Array of testimonial objects |
variant | "default" | "compact" | "card" | "default" | Visual style variant |
direction | "normal" | "reverse" | "normal" | Scroll direction |
duration | number | 30000 | Animation duration in ms |
pauseOnHover | boolean | true | Pause animation on hover |
showFade | boolean | true | Show fade effect at edges |
Testimonial
| Prop | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier |
name | string | Yes | Person's name |
role | string | Yes | Job title |
company | string | Yes | Company name |
content | string | Yes | Testimonial text |
rating | number | Yes | Star rating (1-5) |
image | string | No | Avatar 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