Rich Testimonial Section
Recently added component.
Preview
Live interactive preview.
Installation
Add this component to your project using the CLI:
terminal
npx -y vui-registry-cli-v1@latest add rich-testimonial-sectionSource Code
rich-testimonial-section.tsx
"use client";
import React, { useRef, useState } from "react";
import { motion, useScroll, useTransform, AnimatePresence } from "framer-motion";
import Image from "next/image";
const testimonials = [
{
name: "Alex Rivera",
role: "Frontend Architect",
text: "Velocity UI didn't just speed up our workflow; it completely redefined our standard for quality. The components feel truly premium out of the box.",
avatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&h=150&fit=crop&crop=faces"
},
{
name: "Priya Sharma",
role: "Design Lead",
text: "As a designer, I'm extremely picky about typography and spacing. This is the first UI kit I've used that gets the microscopic details exactly right.",
avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=150&h=150&fit=crop&crop=faces"
},
{
name: "Jordan Lee",
role: "Indie Hacker",
text: "Shipped my SaaS in a weekend. The dark mode implementation and glassmorphic details make it look like I had a full design team.",
avatar: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=150&h=150&fit=crop&crop=faces"
}
];
export default function RichTestimonialSection() {
const containerRef = useRef<HTMLDivElement>(null);
const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start end", "end start"]
});
const y1 = useTransform(scrollYProgress, [0, 1], [40, -40]);
const y2 = useTransform(scrollYProgress, [0, 1], [80, -80]);
return (
<section ref={containerRef} className="relative w-full bg-background py-32 overflow-hidden selection:bg-foreground selection:text-background">
<div className="max-w-7xl mx-auto px-6 relative z-20">
{/* Header */}
<div className="flex flex-col items-start mb-24">
<motion.div
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="flex items-center gap-4 mb-6"
>
<div className="w-8 h-[1px] bg-foreground/30" />
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-muted-foreground">
Social Proof
</span>
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-4xl md:text-6xl font-bold tracking-tighter text-foreground max-w-3xl leading-[1.1]"
>
Loved by the best <br />
<span className="text-muted-foreground font-medium">engineering teams.</span>
</motion.h2>
</div>
{/* Testimonials Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{testimonials.map((t, idx) => (
<motion.div
key={idx}
style={{ y: idx === 1 ? y2 : y1 }}
onMouseEnter={() => setHoveredIdx(idx)}
onMouseLeave={() => setHoveredIdx(null)}
className="relative group cursor-crosshair"
>
<div className="relative bg-card dark:bg-[#0A0A0A] p-8 h-full flex flex-col justify-between transition-all duration-500">
{/* Corner Crosshairs */}
<div className="absolute top-0 left-0 w-2 h-2 border-t border-l border-foreground/20 -translate-x-[1px] -translate-y-[1px]" />
<div className="absolute top-0 right-0 w-2 h-2 border-t border-r border-foreground/20 translate-x-[1px] -translate-y-[1px]" />
<div className="absolute bottom-0 left-0 w-2 h-2 border-b border-l border-foreground/20 -translate-x-[1px] translate-y-[1px]" />
<div className="absolute bottom-0 right-0 w-2 h-2 border-b border-r border-foreground/20 translate-x-[1px] translate-y-[1px]" />
{/* Border that fills on hover */}
<div className="absolute inset-0 border border-border transition-colors duration-500 group-hover:border-foreground/20" />
<div className="relative z-10">
<div className="flex justify-between items-start mb-8">
<svg className="w-6 h-6 text-foreground/20 group-hover:text-foreground transition-colors duration-500" fill="currentColor" viewBox="0 0 24 24">
<path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z" />
</svg>
<span className="text-[9px] font-mono text-muted-foreground uppercase tracking-widest">
ID_0{idx + 1}
</span>
</div>
<p className="text-foreground text-lg leading-relaxed tracking-tight mb-8 font-medium">
"{t.text}"
</p>
</div>
<div className="relative z-10 flex items-center gap-4 pt-6 border-t border-border/50">
<div className="relative w-10 h-10 overflow-hidden">
<Image
src={t.avatar}
alt={t.name}
fill
className="object-cover grayscale group-hover:grayscale-0 group-hover:scale-110 transition-all duration-700 ease-out"
/>
</div>
<div>
<h4 className="text-sm font-bold text-foreground">{t.name}</h4>
<p className="text-[10px] font-bold text-muted-foreground uppercase tracking-widest mt-1">{t.role}</p>
</div>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}