Docs
Testimonial Grid

Testimonial Grid

A modern grid layout for displaying customer testimonials. Features 2 or 3 column layouts, ratings, verified badges, and smooth hover effects.

Loved by thousands of customers

See what our customers have to say about their experience

Verified

"This platform has completely transformed how we manage our business. The intuitive interface and powerful features have saved us countless hours. Highly recommended!"

SJ
Sarah Johnson
CEO · TechCorp
Verified

"The best investment we've made this year. The customer support is exceptional, and the platform scales perfectly with our growing needs."

MC
Michael Chen
Product Manager · InnovateLabs
Verified

"I was skeptical at first, but after using it for three months, I can't imagine going back. The analytics alone are worth the price."

ER
Emily Rodriguez
Marketing Director · GrowthCo

"Seamless integration with our existing tools and excellent documentation. The development team clearly knows what they're doing."

DK
David Kim
CTO · DataFlow
Verified

"The automation features have reduced our manual work by 80%. Our team is more productive than ever, and we're seeing real ROI."

LA
Lisa Anderson
Operations Manager · Streamline Inc
Verified

"As a startup, we needed something reliable and affordable. This platform delivered on both fronts and continues to exceed our expectations."

JW
James Wilson
Founder · StartupXYZ
4.9/5
Average Rating
10K+
Happy Customers
99%
Satisfaction Rate

What our enterprise clients say

Trusted by leading companies worldwide

Verified

"Our sales team's productivity has increased by 45% since implementing this solution. The reporting features give us insights we never had before."

RG
Rachel Green
VP of Sales · SalesForce Pro
Verified

"Clean API, great documentation, and responsive support team. Everything a developer could ask for in a platform."

TM
Tom Martinez
Engineering Lead · DevTools Inc

"Our customer satisfaction scores have improved dramatically. The platform makes it easy to deliver exceptional service."

AF
Amanda Foster
Customer Success · ClientFirst
Verified

"The ROI was clear within the first quarter. Excellent value for money and the features keep getting better with each update."

CT
Chris Taylor
Finance Director · FinTech Solutions
4.9/5
Average Rating
10K+
Happy Customers
99%
Satisfaction Rate

Installation

Install dependencies

npm install framer-motion

Copy and paste the following code into your project.

"use client";
 
import { motion } from "framer-motion";
import { Star, Quote } from "lucide-react";
import Image from "next/image";
 
interface Testimonial {
  id: string;
  name: string;
  role: string;
  company: string;
  content: string;
  avatar?: string;
  rating: number;
  image?: string;
  verified?: boolean;
}
 
interface TestimonialGridProps {
  title?: string;
  description?: string;
  testimonials: Testimonial[];
  columns?: 2 | 3;
  className?: string;
}
 
export function TestimonialGrid({
  title = "Loved by thousands of customers",
  description = "See what our customers have to say about their experience",
  testimonials,
  columns = 3,
  className = "",
}: TestimonialGridProps) {
  const gridCols =
    columns === 2 ? "md:grid-cols-2" : "md:grid-cols-2 lg:grid-cols-3";
 
  return (
    <section className={`w-full bg-white py-20 dark:bg-black ${className}`}>
      <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
        {/* Header */}
        <div className="mb-16 text-center">
          <h2 className="mb-4 text-4xl font-bold text-zinc-900 dark:text-white sm:text-5xl">
            {title}
          </h2>
          <p className="mx-auto max-w-2xl text-lg text-zinc-600 dark:text-zinc-400">
            {description}
          </p>
        </div>
 
        {/* Testimonials Grid */}
        <div className={`grid gap-6 ${gridCols}`}>
          {testimonials.map((testimonial, index) => {
            return (
              <motion.div
                key={testimonial.id}
                initial={{ opacity: 0, y: 20 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ delay: index * 0.1 }}
                className="group relative flex flex-col rounded-2xl border border-zinc-200 bg-white p-8 transition-all duration-300 hover:border-zinc-300 hover:shadow-lg dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-zinc-700"
              >
                {/* Top Section */}
                <div className="mb-6 flex items-start justify-between">
                  {/* Rating */}
                  <div className="flex gap-0.5">
                    {Array.from({ length: 5 }).map((_, i) => (
                      <Star
                        key={i}
                        className={`h-4 w-4 ${
                          i < testimonial.rating
                            ? "fill-yellow-400 text-yellow-400"
                            : "fill-zinc-200 text-zinc-200 dark:fill-zinc-800 dark:text-zinc-800"
                        }`}
                      />
                    ))}
                  </div>
 
                  {/* Verified Badge */}
                  {testimonial.verified && (
                    <div className="flex items-center gap-1.5 rounded-full border border-emerald-200 bg-emerald-50 px-2.5 py-1 text-xs font-medium text-emerald-700 dark:border-emerald-900 dark:bg-emerald-950 dark:text-emerald-400">
                      <svg
                        className="h-3 w-3"
                        fill="currentColor"
                        viewBox="0 0 20 20"
                      >
                        <path
                          fillRule="evenodd"
                          d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
                          clipRule="evenodd"
                        />
                      </svg>
                      Verified
                    </div>
                  )}
                </div>
 
                {/* Content */}
                <p className="mb-8 flex-1 text-base leading-relaxed text-zinc-700 dark:text-zinc-300">
                  "{testimonial.content}"
                </p>
 
                {/* Author Section */}
                <div className="flex items-center gap-3 border-t border-zinc-100 pt-6 dark:border-zinc-800">
                  {/* Avatar */}
                  <div className="relative h-11 w-11 flex-shrink-0 overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800">
                    {testimonial.avatar ? (
                      <Image
                        src={testimonial.avatar}
                        alt={testimonial.name}
                        fill
                        className="h-full w-full object-cover"
                      />
                    ) : (
                      <div className="flex h-full w-full items-center justify-center text-sm font-semibold text-zinc-600 dark:text-zinc-400">
                        {testimonial.name
                          .split(" ")
                          .map((n) => n[0])
                          .join("")
                          .toUpperCase()
                          .slice(0, 2)}
                      </div>
                    )}
                  </div>
 
                  {/* Info */}
                  <div className="flex-1 min-w-0">
                    <div className="truncate font-semibold text-zinc-900 dark:text-white">
                      {testimonial.name}
                    </div>
                    <div className="truncate text-sm text-zinc-600 dark:text-zinc-400">
                      {testimonial.role} · {testimonial.company}
                    </div>
                  </div>
 
                  {/* Quote Icon */}
                  <div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-zinc-50 transition-colors duration-300 group-hover:bg-zinc-100 dark:bg-zinc-900 dark:group-hover:bg-zinc-800">
                    <Quote className="h-4 w-4 text-zinc-400 dark:text-zinc-600" />
                  </div>
                </div>
              </motion.div>
            );
          })}
        </div>
 
        {/* Bottom Stats */}
        <div className="mt-16 grid gap-8 sm:grid-cols-3">
          <div className="text-center">
            <div className="mb-2 text-4xl font-bold text-zinc-900 dark:text-white">
              4.9/5
            </div>
            <div className="text-sm text-zinc-600 dark:text-zinc-400">
              Average Rating
            </div>
          </div>
          <div className="text-center">
            <div className="mb-2 text-4xl font-bold text-zinc-900 dark:text-white">
              10K+
            </div>
            <div className="text-sm text-zinc-600 dark:text-zinc-400">
              Happy Customers
            </div>
          </div>
          <div className="text-center">
            <div className="mb-2 text-4xl font-bold text-zinc-900 dark:text-white">
              99%
            </div>
            <div className="text-sm text-zinc-600 dark:text-zinc-400">
              Satisfaction Rate
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Update the import paths to match your project setup.

Features

  • Flexible columns - Choose between 2 or 3 column layouts
  • Star ratings - Visual 5-star rating display
  • Verified badges - Show verified customer testimonials
  • Avatar support - Display customer photos or initials
  • Hover effects - Smooth border and shadow animations
  • Quote icons - Visual quote indicators
  • Bottom stats - Display aggregate statistics
  • Responsive grid - Adapts to all screen sizes
  • Light/dark mode - Full theme support
  • TypeScript support - Full type safety

Usage

Basic Usage (3 Columns)

import TestimonialGrid from "@/components/ui/testimonial-grid";
 
const testimonials = [
  {
    id: "1",
    name: "Sarah Johnson",
    role: "CEO",
    company: "TechCorp",
    content: "This platform has completely transformed our business...",
    rating: 5,
    verified: true,
  },
  // ... more testimonials
];
 
export default function Page() {
  return <TestimonialGrid testimonials={testimonials} />;
}

2 Column Layout

<TestimonialGrid testimonials={testimonials} columns={2} />

With Custom Title

<TestimonialGrid
  title="What our customers say"
  description="Real feedback from real users"
  testimonials={testimonials}
/>

With Avatars

const testimonials = [
  {
    id: "1",
    name: "Sarah Johnson",
    role: "CEO",
    company: "TechCorp",
    content: "Amazing platform!",
    rating: 5,
    verified: true,
    avatar: "/avatars/sarah.jpg", // Add avatar URL
  },
];

Props

TestimonialGridProps

PropTypeDefaultDescription
testimonialsTestimonial[]RequiredArray of testimonial objects
titlestring"Loved by thousands..."Section title
descriptionstring"See what our customers..."Section description
columns2 | 33Number of columns in grid
classNamestring""Additional CSS classes

Testimonial

PropTypeRequiredDescription
idstringYesUnique identifier
namestringYesCustomer name
rolestringYesCustomer job title
companystringYesCustomer company
contentstringYesTestimonial text
ratingnumberYesRating (1-5)
avatarstringNoAvatar image URL
verifiedbooleanNoShow verified badge
imagestringNoAdditional image

TypeScript Interface

interface Testimonial {
  id: string;
  name: string;
  role: string;
  company: string;
  content: string;
  avatar?: string;
  rating: number;
  image?: string;
  verified?: boolean;
}
 
interface TestimonialGridProps {
  title?: string;
  description?: string;
  testimonials: Testimonial[];
  columns?: 2 | 3;
  className?: string;
}

Use Cases

Perfect for:

  • SaaS landing pages
  • Product pages
  • Service offerings
  • Case study sections
  • Social proof displays
  • Customer success stories
  • Review showcases
  • Trust building sections
  • About pages
  • Pricing pages (social proof)

Customization

Change Hover Effects

// Modify the hover classes in the card
className =
  "... hover:border-emerald-300 hover:shadow-xl dark:hover:border-emerald-700";

Remove Verified Badges

// Simply don't include verified property
const testimonials = [
  {
    id: "1",
    name: "John Doe",
    // ... other props
    // No verified property
  },
];

Custom Stats

// Modify the bottom stats section
<div className="mt-16 grid gap-8 sm:grid-cols-3">
  <div className="text-center">
    <div className="mb-2 text-4xl font-bold">5.0/5</div>
    <div className="text-sm">Your Custom Stat</div>
  </div>
  {/* ... more stats */}
</div>

Add Company Logos

// Add to testimonial interface
interface Testimonial {
  // ... existing props
  companyLogo?: string;
}
 
// In the component, add after the company name
{
  testimonial.companyLogo && (
    <img
      src={testimonial.companyLogo}
      alt={testimonial.company}
      className="mt-2 h-5 w-auto opacity-60"
    />
  );
}

Remove Quote Icon

// Simply remove the quote icon div from the author section
// Delete this section:
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center...">
  <Quote className="h-4 w-4..." />
</div>

Change Avatar Style

// Modify the avatar background
className = "... bg-blue-100 dark:bg-blue-900"; // Colored background
// Or use a border
className = "... border-2 border-zinc-200 dark:border-zinc-700";

Add Card Numbers

// Add a number indicator in the top-left
<div className="absolute left-4 top-4 text-xs font-bold text-zinc-300 dark:text-zinc-700">
  #{index + 1}
</div>

Change Grid Gap

// Modify the grid classes
<div className={`grid gap-8 ${gridCols}`}> {/* Changed from gap-6 to gap-8 */}

Animation Details

  • Staggered load - Cards appear sequentially (0.1s delay)
  • Hover border - Border color subtly changes
  • Hover shadow - Shadow increases smoothly
  • Quote icon - Background color changes on hover
  • No overlays - Clean, minimal animations
  • All transitions - 300ms duration

Accessibility

  • Semantic HTML - Proper heading hierarchy
  • Alt text - Images have descriptive alt text
  • Color contrast - WCAG AA compliant
  • Keyboard navigation - All interactive elements focusable
  • Screen readers - Proper ARIA labels

Performance

  • Lazy animations - Only animates visible cards
  • Optimized images - Use next/image for avatars
  • Minimal re-renders - Efficient state management
  • GPU acceleration - Uses transform and opacity