Docs
Review Cards

Review Cards

Detailed review cards with ratings, author information, and helpful counts. Perfect for showcasing customer reviews and testimonials.

Trusted by industry leaders

See why companies choose us

G2

Boosted our sales by 60%

"The lead management and pipeline features are outstanding. Our sales team adopted it immediately, and we're closing deals faster than ever. The mobile app is also a huge plus for our field team."

RG
Rachel Green
Verified
VP of Sales · SalesForce Pro
3 days ago
19 helpful
Capterra

Developer-friendly and powerful

"Clean API, excellent documentation, and great developer experience. We integrated it into our stack in less than a day. The webhook system is reliable and the error handling is top-notch."

TM
Tom Martinez
Verified
Head of Engineering · DevTools Inc
1 week ago
22 helpful
Trustpilot

Our customers love it

"We use this to manage all our customer interactions and it's been phenomenal. The automation saves us time, and our customer satisfaction scores have improved by 25%. Highly recommended!"

AF
Amanda Foster
Customer Success Manager · ClientFirst
1 week ago
14 helpful
G2

Clear ROI within 3 months

"The pricing is transparent, the value is clear, and we saw positive ROI within the first quarter. The financial reporting features have also helped us make better business decisions."

CT
Chris Taylor
Verified
Finance Director · FinTech Solutions
2 weeks ago
16 helpful
4.9/5
Average Rating
2,500+
Total Reviews
98%
Recommend Us

Installation

Install dependencies

npm install framer-motion lucide-react

Copy and paste the following code into your project.

"use client";
 
import { motion } from "framer-motion";
import { Star, ThumbsUp } from "lucide-react";
 
interface Review {
  id: string;
  author: {
    name: string;
    role: string;
    company: string;
    avatar?: string;
  };
  rating: number;
  title: string;
  content: string;
  date: string;
  verified?: boolean;
  helpful?: number;
  platform?: string;
}
 
interface ReviewCardsProps {
  title?: string;
  description?: string;
  reviews: Review[];
  columns?: 2 | 3;
  className?: string;
}
 
export function ReviewCards({
  title = "What our customers are saying",
  description = "Real reviews from real users",
  reviews,
  columns = 3,
  className = "",
}: ReviewCardsProps) {
  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">
        <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>
 
        {/* Reviews Grid */}
        <div className={`grid gap-6 ${gridCols}`}>
          {reviews.map((review, index) => (
            <motion.article
              key={review.id}
              initial={{ opacity: 0, y: 20 }}
              animate={{ opacity: 1, y: 0 }}
              transition={{ delay: index * 0.1 }}
              className="group 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"
            >
              <div className="mb-6 flex items-start justify-between">
                <div className="flex gap-0.5">
                  {Array.from({ length: 5 }).map((_, i) => (
                    <Star
                      key={i}
                      className={`h-4 w-4 ${
                        i < review.rating
                          ? "fill-yellow-400 text-yellow-400"
                          : "fill-zinc-200 text-zinc-200 dark:fill-zinc-800 dark:text-zinc-800"
                      }`}
                    />
                  ))}
                </div>
 
                {review.platform && (
                  <div className="rounded-full bg-zinc-100 px-2.5 py-1 text-xs font-medium text-zinc-700 dark:bg-zinc-900 dark:text-zinc-300">
                    {review.platform}
                  </div>
                )}
              </div>
 
              <h3 className="mb-3 text-lg font-bold text-zinc-900 dark:text-white">
                {review.title}
              </h3>
 
              <p className="mb-6 flex-1 text-base leading-relaxed text-zinc-700 dark:text-zinc-300">
                "{review.content}"
              </p>
 
              <div className="space-y-4 border-t border-zinc-100 pt-6 dark:border-zinc-800">
                <div className="flex items-center gap-3">
                  <div className="relative h-10 w-10 flex-shrink-0 overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800">
                    {review.author.avatar ? (
                      <img
                        src={review.author.avatar}
                        alt={review.author.name}
                        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">
                        {review.author.name
                          .split(" ")
                          .map((n) => n[0])
                          .join("")
                          .toUpperCase()
                          .slice(0, 2)}
                      </div>
                    )}
                  </div>
 
                  <div className="min-w-0 flex-1">
                    <div className="flex items-center gap-2">
                      <div className="truncate font-semibold text-zinc-900 dark:text-white">
                        {review.author.name}
                      </div>
                      {review.verified && (
                        <div className="flex items-center gap-1 rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700 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>
                    <div className="truncate text-sm text-zinc-600 dark:text-zinc-400">
                      {review.author.role} · {review.author.company}
                    </div>
                  </div>
                </div>
 
                <div className="flex items-center justify-between text-sm">
                  <div className="text-zinc-500 dark:text-zinc-500">
                    {review.date}
                  </div>
                  {review.helpful !== undefined && (
                    <div className="flex items-center gap-1.5 text-zinc-600 dark:text-zinc-400">
                      <ThumbsUp className="h-3.5 w-3.5" />
                      <span className="text-xs font-medium">
                        {review.helpful} helpful
                      </span>
                    </div>
                  )}
                </div>
              </div>
            </motion.article>
          ))}
        </div>
 
        <div className="mt-16 rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-800 dark:bg-zinc-900">
          <div className="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">
                2,500+
              </div>
              <div className="text-sm text-zinc-600 dark:text-zinc-400">
                Total Reviews
              </div>
            </div>
            <div className="text-center">
              <div className="mb-2 text-4xl font-bold text-zinc-900 dark:text-white">
                98%
              </div>
              <div className="text-sm text-zinc-600 dark:text-zinc-400">
                Recommend Us
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Features

  • Star ratings - Visual 5-star rating display
  • Verified badges - Show verified customer reviews
  • Platform badges - Display review source (G2, Capterra, etc.)
  • Author info - Name, role, company with avatar
  • Helpful counts - Show how many found review helpful
  • Review dates - Display when review was posted
  • Flexible columns - Choose 2 or 3 column layouts
  • Bottom stats - Aggregate review statistics
  • Hover effects - Smooth border and shadow animations
  • Light/dark mode - Full theme support
  • TypeScript support - Full type safety

Usage

Basic Usage (3 Columns)

import ReviewCards from "@/components/ui/review-cards";
 
const reviews = [
  {
    id: "1",
    author: {
      name: "Sarah Mitchell",
      role: "Product Manager",
      company: "TechFlow Inc",
    },
    rating: 5,
    title: "Game changer for our team",
    content: "This platform has completely transformed...",
    date: "2 days ago",
    verified: true,
    helpful: 24,
    platform: "G2",
  },
  // ... more reviews
];
 
export default function Page() {
  return <ReviewCards reviews={reviews} />;
}

2 Column Layout

<ReviewCards reviews={reviews} columns={2} />

With Custom Title

<ReviewCards
  title="Customer Reviews"
  description="Real feedback from real users"
  reviews={reviews}
/>

Props

ReviewCardsProps

PropTypeDefaultDescription
reviewsReview[]RequiredArray of review objects
titlestring"What our customers..."Section title
descriptionstring"Real reviews from..."Section description
columns2 | 33Number of columns
classNamestring""Additional CSS classes

Review

PropTypeRequiredDescription
idstringYesUnique identifier
authorAuthorYesAuthor information object
ratingnumberYesRating (1-5)
titlestringYesReview title
contentstringYesReview content
datestringYesReview date
verifiedbooleanNoShow verified badge
helpfulnumberNoHelpful count
platformstringNoReview platform name

Author

PropTypeRequiredDescription
namestringYesAuthor name
rolestringYesAuthor job title
companystringYesAuthor company
avatarstringNoAvatar image URL

TypeScript Interface

interface Review {
  id: string;
  author: {
    name: string;
    role: string;
    company: string;
    avatar?: string;
  };
  rating: number;
  title: string;
  content: string;
  date: string;
  verified?: boolean;
  helpful?: number;
  platform?: string;
}
 
interface ReviewCardsProps {
  title?: string;
  description?: string;
  reviews: Review[];
  columns?: 2 | 3;
  className?: string;
}

Grid Layouts

3 Column (Default)

  • Mobile: 1 column
  • Tablet (md+): 2 columns
  • Desktop (lg+): 3 columns

2 Column

  • Mobile: 1 column
  • Tablet (md+): 2 columns

Features Breakdown

Star Ratings

  • Visual 5-star display
  • Filled stars in yellow
  • Empty stars in gray
  • Supports 1-5 ratings

Verified Badge

  • Green badge with checkmark
  • Shows "Verified" text
  • Optional per review
  • Builds trust and credibility

Platform Badge

  • Gray badge in top-right
  • Shows review source
  • Examples: G2, Capterra, Trustpilot
  • Optional per review

Author Section

  • Avatar with initials fallback
  • Name with verified badge
  • Role and company
  • Truncates long text

Helpful Count

  • Thumbs up icon
  • Shows helpful count
  • Bottom-right of card
  • Optional per review

Review Date

  • Relative dates work best
  • Examples: "2 days ago", "1 week ago"
  • Bottom-left of card
  • Always displayed

Styling

  • Background: White/Black with theme support
  • Cards: White/Zinc-950 with borders
  • Text: Zinc colors with dark mode
  • Borders: Zinc-200/800 default, zinc-300/700 on hover
  • Stars: Yellow-400 filled, Zinc-200/800 empty
  • Verified Badge: Emerald-50/950 with border
  • Platform Badge: Zinc-100/900 background
  • Avatar: Zinc-100/800 background
  • Stats Card: Zinc-50/900 background

Use Cases

Perfect for:

  • SaaS landing pages
  • Product pages
  • Review sections
  • Social proof displays
  • Case study pages
  • Testimonial pages
  • Trust building sections
  • Conversion optimization

Best Practices

  1. Number of reviews - Use 3, 6, or 9 for best layout
  2. Content length - Keep reviews 2-4 sentences (50-100 words)
  3. Ratings - Mix of 4-5 star reviews for authenticity
  4. Verified badges - Use for 50-70% of reviews
  5. Platforms - Show diverse review sources
  6. Dates - Use recent dates (within 2-4 weeks)
  7. Helpful counts - Use realistic numbers (10-30)
  8. Titles - Keep concise and descriptive
  9. Authors - Use real names and companies
  10. Mix ratings - Include some 4-star reviews

Customization

Change Star Color

// Modify the star className
className={`h-4 w-4 ${
  i < review.rating
    ? "fill-blue-400 text-blue-400" // Changed from yellow to blue
    : "fill-zinc-200 text-zinc-200 dark:fill-zinc-800 dark:text-zinc-800"
}`}

Remove Platform Badges

// Simply don't include platform property in review objects
const reviews = [
  {
    id: "1",
    // ... other props
    // No platform property
  },
];

Add Review Images

// Add to Review interface
interface Review {
  // ... existing props
  images?: string[];
}
 
// In the component, add after content
{
  review.images && review.images.length > 0 && (
    <div className="mb-4 flex gap-2">
      {review.images.map((img, idx) => (
        <img
          key={idx}
          src={img}
          alt={`Review image ${idx + 1}`}
          className="h-20 w-20 rounded-lg object-cover"
        />
      ))}
    </div>
  );
}

Custom Stats

// Modify the bottom stats section
<div className="grid gap-8 sm:grid-cols-4">
  <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 Response from Company

// Add to Review interface
interface Review {
  // ... existing props
  response?: {
    author: string;
    content: string;
    date: string;
  };
}
 
// In the component, add after footer
{
  review.response && (
    <div className="mt-4 rounded-lg border-l-4 border-blue-500 bg-blue-50 p-4 dark:bg-blue-950">
      <div className="mb-1 text-sm font-semibold text-blue-900 dark:text-blue-100">
        Response from {review.response.author}
      </div>
      <p className="text-sm text-blue-800 dark:text-blue-200">
        {review.response.content}
      </p>
      <div className="mt-2 text-xs text-blue-600 dark:text-blue-400">
        {review.response.date}
      </div>
    </div>
  );
}

Change Card Height

// The cards use flex-col with flex-1 on content
// To set minimum height:
className = "... min-h-[400px]";

Animation Details

  • Staggered load - Cards appear sequentially (0.1s delay)
  • Fade in - Opacity 0 to 1
  • Slide up - 20px vertical movement
  • Hover border - Border color changes
  • Hover shadow - Shadow increases
  • All transitions - 300ms duration

Accessibility

  • Semantic HTML - Uses article tag for reviews
  • Alt text - Avatar 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 - Static content after load
  • GPU acceleration - Uses transform and opacity

Common Patterns

With Section Divider

<ReviewCards
  reviews={reviews}
  className="border-t border-zinc-200 dark:border-zinc-800"
/>

Compact Version

<ReviewCards
  title=""
  description=""
  reviews={reviews}
  className="py-12" // Reduced padding
/>
<ReviewCards
  title="Featured Reviews"
  description="Handpicked reviews from our top customers"
  reviews={featuredReviews}
  columns={2}
/>