Testimonial Card
A card component for displaying testimonials with a layered, visually engaging design.
Full-Featured Example
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.
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.
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.
Installation
CLI
Installation via the CLI is coming soon. For now, please follow the manual installation instructions below.
Manual
-
This component uses the
Avatar
component. Make sure to add it to your project:npx shadcn-ui@latest add avatar
-
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
Prop | Type | Default | Description |
---|---|---|---|
src | string | - | The source URL for the avatar image. |
alt | string | - | The alternative text for the avatar image. |
fallback | React.ReactNode | - | The content to display as a fallback if the image fails to load. |
...props | AvatarProps | - | Inherits all props from the shadcn/ui Avatar component. |