deepmoon

Testimonial Card

We swapped our whole component layer over a weekend. The token discipline made theming trivial.
Priya ShahHead of Design, Northwind
The accessibility work alone would have taken us a month to get right on our own.
Marcus WebbEngineering Lead

Installation

$ npx shadcn@latest add https://deepmoon.dev/r/testimonial-card.json

Props

TestimonialCardProps
PropTypeRequired
quotestringYes
authorstringYes
rolestringNo
companystringNo
avatarSrcstringNo

Code

testimonial-card.tsx
import * as React from "react";

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

export interface TestimonialCardProps extends React.ComponentPropsWithoutRef<"figure"> {
  quote: string;
  author: string;
  role?: string;
  company?: string;
  avatarSrc?: string;
}

export function TestimonialCard({
  quote,
  author,
  role,
  company,
  avatarSrc,
  className,
  ...props
}: TestimonialCardProps) {
  return (
    <figure
      className={cn("flex flex-col gap-4 rounded-lg border border-border bg-card p-6 text-card-foreground", className)}
      {...props}
    >
      <blockquote className="text-sm leading-relaxed text-foreground">&ldquo;{quote}&rdquo;</blockquote>
      <figcaption className="flex items-center gap-3">
        {avatarSrc ? (
          <img src={avatarSrc} alt="" className="size-9 shrink-0 rounded-full object-cover" />
        ) : (
          <span
            aria-hidden="true"
            className="flex size-9 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground"
          >
            {author.slice(0, 1).toUpperCase()}
          </span>
        )}
        <div className="flex flex-col">
          <span className="text-sm font-medium text-foreground">{author}</span>
          {role || company ? (
            <span className="text-xs text-muted-foreground">
              {role}
              {role && company ? ", " : ""}
              {company}
            </span>
          ) : null}
        </div>
      </figcaption>
    </figure>
  );
}