Loved by thousands of customers
See what our customers have to say about their experience
"This platform has completely transformed how we manage our business. The intuitive interface and powerful features have saved us countless hours. Highly recommended!"
"The best investment we've made this year. The customer support is exceptional, and the platform scales perfectly with our growing needs."
"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."
"Seamless integration with our existing tools and excellent documentation. The development team clearly knows what they're doing."
"The automation features have reduced our manual work by 80%. Our team is more productive than ever, and we're seeing real ROI."
"As a startup, we needed something reliable and affordable. This platform delivered on both fronts and continues to exceed our expectations."
What our enterprise clients say
Trusted by leading companies worldwide
"Our sales team's productivity has increased by 45% since implementing this solution. The reporting features give us insights we never had before."
"Clean API, great documentation, and responsive support team. Everything a developer could ask for in a platform."
"Our customer satisfaction scores have improved dramatically. The platform makes it easy to deliver exceptional service."
"The ROI was clear within the first quarter. Excellent value for money and the features keep getting better with each update."
Installation
Install dependencies
npm install framer-motionCopy 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
| Prop | Type | Default | Description |
|---|---|---|---|
testimonials | Testimonial[] | Required | Array of testimonial objects |
title | string | "Loved by thousands..." | Section title |
description | string | "See what our customers..." | Section description |
columns | 2 | 3 | 3 | Number of columns in grid |
className | string | "" | Additional CSS classes |
Testimonial
| Prop | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier |
name | string | Yes | Customer name |
role | string | Yes | Customer job title |
company | string | Yes | Customer company |
content | string | Yes | Testimonial text |
rating | number | Yes | Rating (1-5) |
avatar | string | No | Avatar image URL |
verified | boolean | No | Show verified badge |
image | string | No | Additional 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