Testimonials 3
Testimonials 3 is a React and Tailwind CSS layout component from SmoothUI, licensed MIT. Copy it and paste it into your project.
SmoothUI
MIT
layout
What it is
Testimonials 3 is a React and Tailwind CSS layout component from SmoothUI, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
Needs
npm i lucide-react motion
Licence
This component comes from SmoothUI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Testimonials 3
"use client";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar";
import { cn } from "@/lib/utils";
import { getAvatarUrl, getTestimonials } from "@/lib/smoothui-data";
import { Star } from "lucide-react";
import { motion, useReducedMotion } from "motion/react";
const testimonials = getTestimonials(4);
export function TestimonialsStars() {
const shouldReduceMotion = useReducedMotion();
return (
<section>
<div className="py-24">
<div className="container mx-auto w-full max-w-5xl px-6">
<motion.div
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}
className="mb-12"
initial={
shouldReduceMotion ? { opacity: 1 } : { opacity: 0, y: 20 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: { duration: 0.6, ease: [0.22, 1, 0.36, 1] }
}
>
<h2 className="font-semibold text-4xl text-foreground">
Developer Reviews
</h2>
<p className="my-4 text-balance text-lg text-muted-foreground">
See what the community is saying about SmoothUI. Real feedback
from developers building amazing user experiences.
</p>
</motion.div>
<motion.div
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1 }}
className="grid 3xl:grid-cols-3 3xl:gap-12 gap-6 lg:grid-cols-2"
initial={shouldReduceMotion ? { opacity: 1 } : { opacity: 0 }}
transition={
shouldReduceMotion
? { duration: 0 }
: { delay: 0.2, duration: 0.6, ease: [0.22, 1, 0.36, 1] }
}
>
{testimonials.map((testimonial, index) => (
<motion.div
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
}
className="group rounded-2xl border border-transparent px-4 py-3 duration-200 hover:border-border hover:bg-background/50"
initial={
shouldReduceMotion ? { opacity: 1 } : { opacity: 0, y: 30 }
}
key={testimonial.name}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15,
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
}
}
whileHover={
shouldReduceMotion
? {}
: {
transition: { duration: 0.2, ease: [0.22, 1, 0.36, 1] },
y: -4,
}
}
>
<motion.div
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, scale: 1 }
}
className="flex gap-1"
initial={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 0, scale: 0.8 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15 + 0.2,
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}
}
>
{Array.from({ length: 5 }).map((_, i) => (
<motion.div
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, scale: 1 }
}
initial={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 0, scale: 0 }
}
key={`${testimonial.name}-star-${i}`}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15 + 0.2 + i * 0.05,
duration: 0.3,
ease: [0.68, -0.55, 0.265, 1.55],
}
}
>
<Star
className={cn(
"size-4 transition-colors duration-200",
i < (testimonial.stars || 0)
? "fill-accent stroke-accent"
: "fill-primary stroke-border"
)}
/>
</motion.div>
))}
</motion.div>
<motion.p
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
}
className="my-4 text-foreground"
initial={
shouldReduceMotion ? { opacity: 1 } : { opacity: 0, y: 10 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15 + 0.4,
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}
}
>
{testimonial.content}
</motion.p>
<motion.div
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, x: 0 }
}
className="flex items-center gap-2"
initial={
shouldReduceMotion ? { opacity: 1 } : { opacity: 0, x: -10 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15 + 0.5,
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}
}
>
<Avatar className="size-6 border border-transparent shadow ring-1 ring-foreground/10">
<AvatarImage
alt={testimonial.name}
src={getAvatarUrl(testimonial.avatar, 48)}
/>
<AvatarFallback>
{testimonial.name.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="font-medium text-foreground text-sm">
{testimonial.name}
</div>
<span
aria-hidden="true"
className="size-1 rounded-full bg-foreground/25"
/>
<span className="text-muted-foreground text-sm">
{testimonial.role}
</span>
</motion.div>
</motion.div>
))}
</motion.div>
</div>
</div>
</section>
);
}
export default TestimonialsStars;