# Wall Of Love Grid

> Wall of love testimonial grid with seven customer quote cards, each carrying a star rating, avatar, name, role, and company.

A shadcn/ui block from the [@7ovr](https://7ovr.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/7ovr/testimonials-3).

- Page, with a live preview: https://shadcn.exchange/r/7ovr/testimonials-3
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/7ovr/testimonials-3-light-f581d9b18cb212b1.png
- Registry item: https://7ovr.com/r/testimonials-3.json
- Categories: testimonials
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/testimonials-3.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@base-ui/react`
- Registry items: `avatar`, `card`

## Files

### `registry/blocks/testimonials/3/testimonials-block.tsx`

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Card } from "@/components/ui/card"
import { IconPlaceholder } from "@/components/icons/icon-placeholder"

const testimonials = [
  {
    quote:
      "We shipped a polished marketing site in a single afternoon. The blocks dropped in cleanly and looked right in both light and dark themes, exactly what our team needed without a single tweak.",
    name: "Maya Chen",
    role: "Founder",
    company: "Northwind",
    avatar: "https://i.pravatar.cc/150?img=47",
    rating: 5,
  },
  {
    quote:
      "Accessible, sharp, and zero config. Our engineers stopped reinventing layouts and started shipping features again within days of adopting Acme. Onboarding went from weeks to a single morning.",
    name: "Priya Nair",
    role: "Engineering Manager",
    company: "Vela",
    avatar: "https://i.pravatar.cc/150?img=32",
    rating: 5,
  },
  {
    quote:
      "Adopting Acme cut our component backlog in half by the end of the first sprint.",
    name: "Tomás Herrera",
    role: "Frontend Lead",
    company: "Cobalt",
    avatar: "https://i.pravatar.cc/150?img=13",
    rating: 4,
  },
  {
    quote:
      "I expected to fight the defaults. Instead everything just composed: spacing, typography, focus states. It feels like it was designed by someone who actually ships product.",
    name: "Sofia Lindqvist",
    role: "Product Designer",
    company: "Fernweh",
    avatar: "https://i.pravatar.cc/150?img=45",
    rating: 5,
  },
  {
    quote:
      "Our agency standardized on Acme across six client projects. Handoffs are clean and reviews are faster.",
    name: "Marcus Bell",
    role: "Creative Director",
    company: "Studio Atlas",
    avatar: "https://i.pravatar.cc/150?img=8",
    rating: 5,
  },
  {
    quote:
      "The dark mode story alone paid for itself. No more one-off overrides scattered through the codebase, and our QA pass on theming dropped to almost nothing.",
    name: "Aisha Rahman",
    role: "Staff Engineer",
    company: "Quanta",
    avatar: "https://i.pravatar.cc/150?img=26",
    rating: 5,
  },
]

function getInitials(name: string) {
  return name
    .split(" ")
    .map((part) => part[0])
    .join("")
}

export default function TestimonialsBlock() {
  return (
    <section className="flex w-full items-center justify-center bg-muted/30 px-6 py-20 text-foreground">
      <div className="mx-auto w-full max-w-6xl">
        <div className="mx-auto max-w-xl text-center">
          <span className="inline-block rounded-md border border-border px-3 py-1 text-xs font-semibold tracking-widest text-muted-foreground uppercase">
            Wall of love
          </span>
          <h2 className="mt-4 font-heading text-4xl font-bold tracking-tight sm:text-5xl">
            Teams ship faster with Acme
          </h2>
          <p className="mt-4 text-base text-muted-foreground">
            Thousands of engineers, designers, and founders build their products
            on Acme. Here is what a few of them have to say.
          </p>
        </div>

        <div className="mt-14 grid grid-cols-1 gap-5 sm:grid-cols-2 md:grid-cols-3">
          {testimonials.map(
            ({ quote, name, role, company, avatar, rating }) => (
              <Card
                key={name}
                className="flex h-full flex-col gap-0 border-border bg-card p-6 transition-colors duration-200 hover:border-foreground/20"
              >
                <div className="flex flex-1 flex-col gap-4">
                  <div className="flex items-center justify-between">
                    <IconPlaceholder
                      lucide="Quote"
                      tabler="IconQuotes"
                      hugeicons="QuotesIcon"
                      phosphor="Quotes"
                      remixicon="RiDoubleQuotesL"
                      className="size-7 text-foreground opacity-20"
                      aria-hidden="true"
                    />
                    <div
                      className="flex items-center gap-0.5"
                      role="img"
                      aria-label={`${rating} out of 5 stars`}
                    >
                      {Array.from({ length: 5 }).map((_, i) => (
                        <IconPlaceholder
                          lucide="Star"
                          tabler="IconStar"
                          hugeicons="StarIcon"
                          phosphor="Star"
                          remixicon="RiStarFill"
                          key={i}
                          className={
                            i < rating
                              ? "size-4 text-primary"
                              : "size-4 text-muted-foreground/30"
                          }
                          aria-hidden="true"
                        />
                      ))}
                    </div>
                  </div>
                  <blockquote className="flex-1 text-[15px] leading-relaxed text-foreground">
                    &ldquo;{quote}&rdquo;
                  </blockquote>
                </div>

                <div className="mt-6 flex items-center gap-3">
                  <Avatar className="size-10 border border-border">
                    <AvatarImage
                      src={avatar}
                      alt={name}
                      className="grayscale"
                    />
                    <AvatarFallback className="text-xs font-semibold">
                      {getInitials(name)}
                    </AvatarFallback>
                  </Avatar>
                  <span className="flex flex-col gap-0.5">
                    <span className="text-sm font-semibold text-foreground">
                      {name}
                    </span>
                    <span className="text-xs text-muted-foreground">
                      {role},{" "}
                      <span className="font-medium text-foreground">
                        {company}
                      </span>
                    </span>
                  </span>
                </div>
              </Card>
            )
          )}
        </div>
      </div>
    </section>
  )
}
```
