# Testimonial Card

> Quote card with a large quote mark, star rating and author, in plain, outlined and filled variants.

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

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/testimonial-card.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-card-demo.tsx"
import { TestimonialCard } from "@/components/testimonial-card"

export default function TestimonialCardDemo() {
  return (
    <div className="flex flex-wrap items-stretch justify-center gap-6">
      <TestimonialCard
        variant="plain"
        quote="We replaced three internal tools with this library and shipped our redesign two weeks early."
        author="Priya Raman"
        role="Head of Product, Lumen"
      />
      <TestimonialCard
        variant="outlined"
        quote="The components are accessible out of the box, which saved our team countless hours of audits."
        author="Tom Becker"
        role="Staff Engineer, Northwind"
        rating={4}
      />
      <TestimonialCard
        variant="filled"
        quote="Clean, composable and beautiful in both light and dark. Our designers actually love it."
        author="Sofia Alvarez"
        role="Design Lead, Orbit"
      />
    </div>
  )
}
```

## Source

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

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

export type TestimonialCardVariant = "plain" | "outlined" | "filled"

export interface TestimonialCardProps extends Omit<React.ComponentProps<"figure">, "children"> {
  quote: string
  author: string
  role: string
  /** 0-5 */
  rating?: number
  variant?: TestimonialCardVariant
}

const variants: Record<TestimonialCardVariant, string> = {
  plain: "bg-transparent",
  outlined: "border bg-card shadow-sm",
  filled: "bg-primary text-primary-foreground",
}

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

export function TestimonialCard({
  quote,
  author,
  role,
  rating = 5,
  variant = "outlined",
  className,
  ...props
}: TestimonialCardProps) {
  const filled = variant === "filled"

  return (
    <figure
      className={cn(
        "relative w-full max-w-sm rounded-2xl p-6",
        variants[variant],
        className
      )}
      {...props}
    >
      <Quote
        className={cn("size-9 rotate-180", filled ? "text-primary-foreground/40" : "text-primary/30")}
        aria-hidden
        fill="currentColor"
        strokeWidth={0}
      />
      {rating > 0 && (
        <div className="mt-3 flex gap-0.5" role="img" aria-label={`Rated ${rating} out of 5`}>
          {Array.from({ length: 5 }, (_, i) => (
            <Star
              key={i}
              className={cn(
                "size-4",
                i < rating
                  ? "fill-amber-400 text-amber-400"
                  : filled
                    ? "text-primary-foreground/30"
                    : "text-muted-foreground/40"
              )}
              aria-hidden
            />
          ))}
        </div>
      )}
      <blockquote className="mt-3 text-base leading-relaxed">{quote}</blockquote>
      <figcaption className="mt-5 flex items-center gap-3">
        <span
          className={cn(
            "flex size-10 items-center justify-center rounded-full text-sm font-semibold",
            filled ? "bg-primary-foreground/15 text-primary-foreground" : "bg-secondary text-secondary-foreground"
          )}
          aria-hidden
        >
          {initials(author)}
        </span>
        <span className="leading-tight">
          <span className="block text-sm font-medium">{author}</span>
          <span className={cn("block text-xs", filled ? "text-primary-foreground/70" : "text-muted-foreground")}>
            {role}
          </span>
        </span>
      </figcaption>
    </figure>
  )
}
```
