Menu

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 testimonial-section

Source 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>
  );
}

Props

Component property reference.

NameTypeDefaultDescription
dataTestimonial[]allTestimonialsArray of testimonial objects to display in the section.
Context Worth Keeping In Orbit

Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.

I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.

I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.