Cards

Testimonial Card

A card component for displaying testimonials with a layered, visually engaging design.

The TestimonialCard is built with composition in mind. This complete example showcases all available sub-components working in concert, including TestimonialCardRating, to create a professional and compelling testimonial.

AH

The architectural elegance of this component is remarkable. It's not just about looking good; the composable API makes it incredibly intuitive to adapt. We integrated it into our design system in less than an hour.

Alex Hartman, Head of Product at QuantumLeap AI

Responsive Layout

The component is designed to be modular and works seamlessly within responsive layouts. This demo shows two cards in a flexbox layout that adapts using container queries. The layout switches from a vertical stack (flex-col) to a side-by-side arrangement (@lg:flex-row) when the parent container has enough space.

DT

I will take what is mine with fire and blood.

Daenerys Targaryen, Mother of Dragons
JS

They were born on the wrong side of the wall. That doesn't make them monsters.

Jon Snow, King in the North

Styling and Theming

Leverage the power of className to create unique themes. This example demonstrates how to customize individual components to achieve distinct styles—repositioning the avatar, changing the accent color and rotation, or even removing elements entirely for a minimalist aesthetic.

CL

When you play the game of thrones, you win or you die. There is no middle ground.

Cersei Lannister, Queen of the Andals and the First Men
PB

Chaos isn't a pit. Chaos is a ladder.

Petyr 'Littlefinger' Baelish, Lord Protector of the Vale

Installation

CLI

Installation via the CLI is coming soon. For now, please follow the manual installation instructions below.

Manual

  1. This component uses the Avatar component. Make sure to add it to your project:

    npx shadcn-ui@latest add avatar
  2. Copy and paste the following code into your project at components/ui/testimonial-card.tsx.

import * as React from 'react';
import { cn } from '@/lib/utils';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';

/**
 * The root container for a testimonial card. Establishes a relative
 * positioning context for its children, like the accent and avatar.
 */
const TestimonialCard = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    // WHY: `relative` is crucial for positioning the Accent and Avatar layers absolutely.
    className={cn('relative w-full max-w-md', className)}
    {...props}
  />
));
TestimonialCard.displayName = 'TestimonialCard';

/**
 * A decorative background layer, positioned absolutely behind the main content.
 * Its visual style (color, offset, rotation) is controlled via className.
 */
const TestimonialCardAccent = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    // WHY: `inset-0` makes it match the parent's size, and `-z-10` ensures it stays behind the content card.
    className={cn(
      'absolute inset-0 -z-10 rounded-xl transition-transform translate-x-2 translate-y-2',
      className
    )}
    {...props}
  />
));
TestimonialCardAccent.displayName = 'TestimonialCardAccent';

/**
 * The main content container for the testimonial, using a `figure` element
 * for semantic correctness with `blockquote` and `figcaption`.
 */
const TestimonialCardContent = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <figure
    ref={ref}
    className={cn(
      'relative flex w-full flex-col gap-6 rounded-xl border bg-card p-6 text-card-foreground shadow-sm ring-2',
      className
    )}
    {...props}
  />
));
TestimonialCardContent.displayName = 'TestimonialCardContent';

interface TestimonialCardAvatarProps
  extends React.ComponentPropsWithoutRef<typeof Avatar> {
  /** The source URL for the avatar image. */
  src?: string;
  /** The alternative text for the avatar image. */
  alt?: string;
  /** The content to display as a fallback if the image fails to load. */
  fallback?: React.ReactNode;
}

/**
 * The floating avatar element, positioned absolutely relative to the root `TestimonialCard`.
 * Extends the `Avatar` component from shadcn/ui.
 */
const TestimonialCardAvatar = React.forwardRef<
  React.ElementRef<typeof Avatar>,
  TestimonialCardAvatarProps
>(({ className, src, alt, fallback, ...props }, ref) => (
  <Avatar
    ref={ref}
    className={cn(
      'absolute h-16 w-16 border-4 border-background z-10 ring-2',
      className
    )}
    {...props}
  >
    <AvatarImage src={src} alt={alt} />
    <AvatarFallback>{fallback}</AvatarFallback>
  </Avatar>
));
TestimonialCardAvatar.displayName = 'TestimonialCardAvatar';

/**
 * The main quote or testimonial text, using a `blockquote` for semantic meaning.
 */
const TestimonialCardQuote = React.forwardRef<
  HTMLQuoteElement,
  React.HTMLAttributes<HTMLQuoteElement>
>(({ className, ...props }, ref) => (
  <blockquote
    ref={ref}
    className={cn('text-base leading-relaxed', className)}
    {...props}
  />
));
TestimonialCardQuote.displayName = 'TestimonialCardQuote';

/**
 * A container for rating elements, such as star icons.
 */
const TestimonialCardRating = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn('flex items-center gap-1', className)}
    {...props}
  />
));
TestimonialCardRating.displayName = 'TestimonialCardRating';

/**
 * The author attribution section, using a `figcaption` to semantically
 * link it to the `figure` in `TestimonialCardContent`.
 */
const TestimonialCardAuthor = React.forwardRef<
  HTMLElement,
  React.HTMLAttributes<HTMLElement>
>(({ className, ...props }, ref) => (
  <figcaption
    ref={ref}
    className={cn('text-sm text-muted-foreground', className)}
    {...props}
  />
));
TestimonialCardAuthor.displayName = 'TestimonialCardAuthor';

export {
  TestimonialCard,
  TestimonialCardAccent,
  TestimonialCardContent,
  TestimonialCardAvatar,
  TestimonialCardQuote,
  TestimonialCardRating,
  TestimonialCardAuthor,
};

Usage

Compose the TestimonialCard parts to build a complete testimonial. The styles and positioning of the accent and avatar layers can be customized with Tailwind CSS classes.

import { Star } from 'lucide-react';
import {
  TestimonialCard,
  TestimonialCardAccent,
  TestimonialCardAuthor,
  TestimonialCardAvatar,
  TestimonialCardContent,
  TestimonialCardQuote,
  TestimonialCardRating,
} from '@/components/ui/testimonial-card';

export default function TestimonialCardDemo() {
  return (
    <div className='p-10'>
      <TestimonialCard>
        <TestimonialCardAccent className='bg-purple-400 -rotate-2' />
        <TestimonialCardAvatar
          src='https://images.unsplash.com/photo-1500648767791-00dcc994a43e'
          alt='Steve, Founder of PollQR.com'
          fallback='ST'
          className='-top-8 -left-8'
        />
        <TestimonialCardContent>
          <TestimonialCardQuote>
            Great value for startups - I didn't know half of these product
            launch directories even existed! GREAT work, refreshing. Highly
            recommended.
          </TestimonialCardQuote>
          <div className='flex flex-col gap-3'>
            <TestimonialCardRating>
              <Star className='h-4 w-4 fill-yellow-400 text-yellow-400' />
              <Star className='h-4 w-4 fill-yellow-400 text-yellow-400' />
              <Star className='h-4 w-4 fill-yellow-400 text-yellow-400' />
              <Star className='h-4 w-4 fill-yellow-400 text-yellow-400' />
              <Star className='h-4 w-4 fill-yellow-400 text-yellow-400' />
            </TestimonialCardRating>
            <TestimonialCardAuthor>
              <span className='font-bold text-foreground'>Steve</span> / Founder
              of PollQR.com
            </TestimonialCardAuthor>
          </div>
        </TestimonialCardContent>
      </TestimonialCard>
    </div>
  );
}

Props

The component is compositional. Only TestimonialCardAvatar has unique props beyond standard HTML attributes. All other components accept standard className and other HTML attributes for their respective elements.

TestimonialCardAvatar

PropTypeDefaultDescription
srcstring-The source URL for the avatar image.
altstring-The alternative text for the avatar image.
fallbackReact.ReactNode-The content to display as a fallback if the image fails to load.
...propsAvatarProps-Inherits all props from the shadcn/ui Avatar component.