Skip to main content

Overview

The Card component is a flexible container with support for multiple sections (header, body, footer, image), visual variants, configurable padding, and hover effects. Perfect for displaying grouped content, articles, product listings, and more.

TypeScript Types

CardProps

interface CardProps extends HTMLAttributes<HTMLDivElement> {
  variant?: 'default' | 'bordered' | 'elevated';
  padding?: 'none' | 'sm' | 'md' | 'lg';
  hoverable?: boolean;
}

Sub-component Props

interface CardHeaderProps extends HTMLAttributes<HTMLDivElement> {}
interface CardBodyProps extends HTMLAttributes<HTMLDivElement> {}
interface CardFooterProps extends HTMLAttributes<HTMLDivElement> {}
interface CardImageProps extends HTMLAttributes<HTMLImageElement> {
  src: string;
  alt: string;
}

Card Component

variant
'default' | 'bordered' | 'elevated'
default:"default"
The visual style of the card:
  • default - Standard card with 1px border
  • bordered - Card with 2px border for emphasis
  • elevated - Card with large shadow for prominence
padding
'none' | 'sm' | 'md' | 'lg'
default:"none"
Internal padding for the card container:
  • none - No padding (use sub-component padding)
  • sm - Small padding (p-3)
  • md - Medium padding (p-5)
  • lg - Large padding (p-7)
hoverable
boolean
default:"false"
When true, applies hover effects:
  • Increases shadow (hover:shadow-xl)
  • Slight upward translation (hover:-translate-y-1)
  • Cursor changes to pointer
  • Smooth transition animation
className
string
Additional CSS classes to apply to the card container.

Sub-components

CardHeader

Header section of the card with default padding of px-5 py-4.
<CardHeader>
  <h2 className="text-xl font-bold">Card Title</h2>
  <p className="text-muted-foreground">Card subtitle</p>
</CardHeader>

CardBody

Main content section with default padding of px-5 py-4.
<CardBody>
  <p>Main card content goes here.</p>
</CardBody>

CardFooter

Footer section with:
  • Top border (border-t border-border)
  • Muted background (bg-muted/50)
  • Default padding of px-5 py-4
<CardFooter>
  <Button>Action</Button>
</CardFooter>

CardImage

Image component for card headers with:
  • Full width (w-full)
  • Fixed height of 192px (h-48)
  • Object cover for proper scaling
  • Requires src and alt props
<CardImage 
  src="https://example.com/image.jpg" 
  alt="Description" 
/>

Variant Examples

<Card variant="default">
  <CardBody>
    <h3 className="text-lg font-semibold">Default Card</h3>
    <p className="text-muted-foreground mt-2">
      Standard card with single border.
    </p>
  </CardBody>
</Card>
Features:
  • Background: bg-card
  • Border: border border-border
  • Subtle, clean appearance

Full Card Structure

import { Card, CardHeader, CardBody, CardFooter, CardImage } from '@/components/Card';
import { Button } from '@/components/Button';

<Card variant="default" hoverable>
  <CardImage 
    src="https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe" 
    alt="Abstract art" 
  />
  <CardHeader>
    <h3 className="text-lg font-semibold">Card Title</h3>
    <p className="text-sm text-muted-foreground">Card subtitle</p>
  </CardHeader>
  <CardBody>
    <p className="text-muted-foreground">
      This is the main content of the card. It can contain any
      elements you need.
    </p>
  </CardBody>
  <CardFooter>
    <Button size="sm" variant="primary">
      Learn More
    </Button>
  </CardFooter>
</Card>

Hoverable Cards

<Card variant="elevated" hoverable onClick={() => console.log('Card clicked')}>
  <CardImage 
    src="https://images.unsplash.com/photo-1557672172-298e090bd0f1" 
    alt="Featured content" 
  />
  <CardBody>
    <h3 className="text-lg font-semibold">Clickable Card</h3>
    <p className="text-muted-foreground mt-2">
      This card lifts up on hover and changes the cursor to pointer.
    </p>
  </CardBody>
</Card>
When hoverable is true:
  • Shadow increases: hover:shadow-xl
  • Lifts up: hover:-translate-y-1
  • Cursor becomes pointer
  • Smooth transition with duration-[var(--transition-normal)]

Padding Options

{/* No padding - use sub-component padding */}
<Card padding="none">
  <CardHeader>Header has its own padding</CardHeader>
  <CardBody>Body has its own padding</CardBody>
</Card>

{/* Small padding */}
<Card padding="sm">
  <p>Content with small padding</p>
</Card>

{/* Medium padding */}
<Card padding="md">
  <p>Content with medium padding</p>
</Card>

{/* Large padding */}
<Card padding="lg">
  <p>Content with large padding</p>
</Card>

Grid Layout Example

import { Card, CardImage, CardBody, CardFooter } from '@/components/Card';
import { Button } from '@/components/Button';

function ProductGrid() {
  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
      <Card variant="default" hoverable>
        <CardImage 
          src="https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe" 
          alt="Product 1" 
        />
        <CardBody>
          <h3 className="text-lg font-semibold">Product Name</h3>
          <p className="text-muted-foreground mt-2">
            Product description goes here.
          </p>
        </CardBody>
        <CardFooter>
          <Button size="sm" variant="primary">
            View Details
          </Button>
        </CardFooter>
      </Card>
      
      <Card variant="bordered" hoverable>
        <CardImage 
          src="https://images.unsplash.com/photo-1558591710-4b4a1ae0f04d" 
          alt="Product 2" 
        />
        <CardBody>
          <h3 className="text-lg font-semibold">Product Name</h3>
          <p className="text-muted-foreground mt-2">
            Another product description.
          </p>
        </CardBody>
        <CardFooter>
          <Button size="sm" variant="secondary">
            Explore
          </Button>
        </CardFooter>
      </Card>
      
      <Card variant="elevated" hoverable>
        <CardImage 
          src="https://images.unsplash.com/photo-1557672172-298e090bd0f1" 
          alt="Product 3" 
        />
        <CardBody>
          <h3 className="text-lg font-semibold">Featured Product</h3>
          <p className="text-muted-foreground mt-2">
            Featured product with elevated styling.
          </p>
        </CardBody>
        <CardFooter>
          <Button size="sm" variant="accent">
            Discover
          </Button>
        </CardFooter>
      </Card>
    </div>
  );
}

Simple Content Cards

{/* Without image */}
<Card variant="bordered" padding="none">
  <CardHeader>
    <h3 className="text-xl font-bold">Article Title</h3>
    <p className="text-sm text-muted-foreground">Published on Jan 1, 2024</p>
  </CardHeader>
  <CardBody>
    <p className="text-muted-foreground">
      Article content and description. This card uses sub-component
      padding instead of card-level padding.
    </p>
  </CardBody>
  <CardFooter>
    <Button size="sm" variant="ghost">Read More</Button>
  </CardFooter>
</Card>

Accessibility

  • Uses semantic HTML (<div> for containers, <img> for images)
  • Images require alt text for screen readers
  • Hoverable cards should be wrapped in proper interactive elements (<button> or <a>) or have appropriate ARIA attributes
  • Maintains proper heading hierarchy when using headings in CardHeader

Best Practices

  • Use default for standard content cards
  • Use bordered to emphasize or separate cards
  • Use elevated for featured or important content that should stand out
When a card is clickable or leads to another page, set hoverable={true} to provide visual feedback. Consider wrapping the entire card in a link or button for better accessibility.
Use the same sub-components in the same order across similar cards for visual consistency (e.g., always CardImage → CardHeader → CardBody → CardFooter).
Always include descriptive alt text for CardImage to ensure accessibility for screen reader users and when images fail to load.
The padding="none" default allows sub-components to control their own spacing. Use card-level padding when you have custom content that doesn’t use sub-components.

Build docs developers (and LLMs) love