# Testimonial Grid

> A masonry-style three-column grid of testimonial cards with initials avatars, roles and star ratings.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/testimonial-grid
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/testimonial-grid.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

```bash
npm install lucide-react
```

## Usage

```tsx title="components/testimonial-grid-demo.tsx"
import { TestimonialGrid } from "@/components/testimonial-grid"

export default function TestimonialGridDemo() {
  return (
    <div className="w-full max-w-5xl">
      <TestimonialGrid
        testimonials={[
          { name: "Maya Chen", role: "Design Lead, Northwind", quote: "We replaced three internal libraries with this one. Our designers and engineers finally speak the same language." },
          { name: "Daniel Okafor", role: "CTO, Lumen Labs", quote: "The components are clean, accessible and easy to bend to our brand. Onboarding new hires got noticeably faster." },
          { name: "Sofia Alvarez", role: "Product Engineer, Fieldwork", rating: 4, quote: "Copy, paste, ship. I like owning the code, and the dark mode just works with zero extra effort." },
          { name: "Liam Fischer", role: "Founder, Tidepool", quote: "Launched our marketing site in a weekend. It looks like we spent months on it." },
          { name: "Priya Nair", role: "Frontend Lead, Orbit", quote: "Keyboard support and focus states are handled properly out of the box, which saved us a full accessibility audit cycle." },
          { name: "Tom Becker", role: "Indie Hacker", rating: 4, quote: "Great defaults and tasteful details." },
        ]}
      />
    </div>
  )
}
```

## Source

```tsx title="components/testimonial-grid.tsx"
import * as React from "react"
import { Star } from "lucide-react"

import { cn } from "@/lib/utils"

export interface TestimonialGridItem {
  quote: string
  name: string
  role: string
  /** 1 to 5, defaults to 5 */
  rating?: number
}

export interface TestimonialGridProps extends React.ComponentProps<"section"> {
  testimonials: TestimonialGridItem[]
}

const tints = [
  "bg-rose-500/15 text-rose-600 dark:text-rose-400",
  "bg-sky-500/15 text-sky-600 dark:text-sky-400",
  "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400",
  "bg-amber-500/15 text-amber-600 dark:text-amber-400",
  "bg-violet-500/15 text-violet-600 dark:text-violet-400",
]

function initials(name: string) {
  return name
    .split(/\s+/)
    .filter(Boolean)
    .slice(0, 2)
    .map((part) => part[0]?.toUpperCase())
    .join("")
}

export function TestimonialGrid({ testimonials, className, ...props }: TestimonialGridProps) {
  return (
    <section className={cn("w-full px-6 py-12", className)} {...props}>
      <ul className="mx-auto max-w-5xl gap-5 space-y-5 sm:columns-2 lg:columns-3">
        {testimonials.map((t, i) => {
          const rating = Math.max(0, Math.min(5, t.rating ?? 5))
          return (
            <li key={t.name} className="break-inside-avoid">
              <figure className="rounded-2xl border bg-card p-5 text-card-foreground shadow-sm">
                <div className="flex gap-0.5" role="img" aria-label={`${rating} out of 5 stars`}>
                  {Array.from({ length: 5 }, (_, s) => (
                    <Star
                      key={s}
                      aria-hidden
                      className={cn("size-4", s < rating ? "fill-amber-400 text-amber-400" : "text-muted-foreground/40")}
                    />
                  ))}
                </div>
                <blockquote className="mt-3 text-sm leading-relaxed">{t.quote}</blockquote>
                <figcaption className="mt-4 flex items-center gap-3">
                  <span
                    aria-hidden
                    className={cn("flex size-9 shrink-0 items-center justify-center rounded-full text-xs font-semibold", tints[i % tints.length])}
                  >
                    {initials(t.name)}
                  </span>
                  <span className="min-w-0">
                    <span className="block truncate text-sm font-medium">{t.name}</span>
                    <span className="block truncate text-xs text-muted-foreground">{t.role}</span>
                  </span>
                </figcaption>
              </figure>
            </li>
          )
        })}
      </ul>
    </section>
  )
}
```
