Innovations

Wall of Love

Twitter/X-style social proof cards with handles, checkmarks, heart counts, and star ratings.

Preview

Source

tsx
"use client";

import { Star } from "lucide-react";
import { testimonials } from "@/lib/placeholders";

const likeCounts: Record<number, number> = {};
testimonials.forEach((t) => {
  likeCounts[t.id] = 10 + ((t.id * 17 + 3) % 90);
});

function HeartIcon({ filled }: { filled?: boolean }) {
  return (
    <svg viewBox="0 0 24 24" className="w-4 h-4" fill={filled ? "currentColor" : "none"} stroke="currentColor" strokeWidth={2}>
      <path strokeLinecap="round" strokeLinejoin="round" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
    </svg>
  );
}

function CheckBadge() {
  return (
    <svg viewBox="0 0 24 24" className="w-4 h-4 text-sky-500" fill="currentColor">
      <path fillRule="evenodd" d="M8.603 3.799A4.49 4.49 0 0112 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 013.498 1.307 4.491 4.491 0 011.307 3.497A4.49 4.49 0 0121.75 12a4.49 4.49 0 01-1.549 3.397 4.491 4.491 0 01-1.307 3.497 4.491 4.491 0 01-3.497 1.307A4.49 4.49 0 0112 21.75a4.49 4.49 0 01-3.397-1.549 4.49 4.49 0 01-3.498-1.307 4.491 4.491 0 01-1.307-3.497A4.49 4.49 0 012.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 011.307-3.497 4.49 4.49 0 013.497-1.307zm7.007 6.387a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z" clipRule="evenodd" />
    </svg>
  );
}

export default function WallOfLove() {
  return (
    <section className="py-20 px-4 sm:px-6 bg-background">
      <div className="max-w-6xl mx-auto">
        {/* Header */}
        <div className="text-center mb-14">
          <span className="inline-block text-xs font-semibold uppercase tracking-widest text-primary bg-primary/10 border border-primary/20 rounded-full px-4 py-1.5 mb-4">
            Wall of Love
          </span>
          <h2 className="text-3xl sm:text-4xl lg:text-5xl font-extrabold tracking-tight text-foreground mb-4">
            People are talking
          </h2>
          <p className="text-lg text-muted-foreground max-w-xl mx-auto">
            Join thousands of happy clients who've shared their experiences.
          </p>
        </div>

        {/* Social post grid */}
        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
          {testimonials.map((t) => {
            const handle = "@" + t.name.split(" ")[0].toLowerCase();
            const likes = likeCounts[t.id];
            return (
              <div
                key={t.id}
                className="bg-card border border-border rounded-2xl p-5 hover:shadow-md transition-shadow duration-300 flex flex-col gap-3"
              >
                {/* Top row */}
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-3">
                    <img
                      src={t.avatar}
                      alt={t.name}
                      className="w-10 h-10 rounded-full object-cover"
                    />
                    <div>
                      <div className="flex items-center gap-1">
                        <span className="font-bold text-sm text-foreground">{t.name}</span>
                        <CheckBadge />
                      </div>
                      <span className="text-xs text-muted-foreground">{handle}</span>
                    </div>
                  </div>
                  {/* X/Twitter logo */}
                  <svg viewBox="0 0 24 24" className="w-5 h-5 text-foreground" fill="currentColor">
                    <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.747l7.73-8.835L2.011 2.25h6.938l4.26 5.632 5.035-5.632zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77z" />
                  </svg>
                </div>

                {/* Quote */}
                <p className="text-sm text-foreground leading-relaxed flex-1">
                  {t.text}
                </p>

                {/* Stars + like */}
                <div className="flex items-center justify-between pt-1 border-t border-border">
                  <div className="flex gap-0.5">
                    {Array.from({ length: 5 }).map((_, i) => (
                      <Star key={i} className="w-3.5 h-3.5 fill-amber-400 text-amber-400" />
                    ))}
                  </div>
                  <button className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-rose-500 transition-colors group">
                    <span className="group-hover:text-rose-500 transition-colors">
                      <HeartIcon />
                    </span>
                    {likes}
                  </button>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}
Claude Code Instructions

CLI Install

npx innovations add wall-of-love

Where to use it

Great for social proof sections on homepages or sales pages. The X/Twitter aesthetic makes it feel modern and credible. In Astro: import WallOfLove from '../components/innovations/testimonials/wall-of-love'; <WallOfLove client:load /> In Next.js: import WallOfLove from '@/components/innovations/testimonials/wall-of-love'; // Place after your features section or before the CTA Edit testimonials in src/lib/placeholders.ts to use real social proof. The heart counts and handles are auto-generated from the data.