Skip to main content

Overview

The Card component is a versatile container for grouping related content. Built with class-variance-authority (cva) and semantic design tokens, it provides a composable API with header, content, and footer sections.

Installation

import {
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
} from "@/components/ui/Card";

Anatomy

The Card component is composed of multiple sub-components:
  • Card: Root container that manages context
  • CardHeader: Optional header section with spacing
  • CardTitle: Title heading (h3 by default, customizable)
  • CardDescription: Secondary text for descriptions
  • CardContent: Main content area that grows to fill space
  • CardFooter: Optional footer with border and actions

Basic Usage

<Card>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
    <CardDescription>
      Card description goes here to describe the content.
    </CardDescription>
  </CardHeader>
  <CardContent>
    <p>This is the main content of the card.</p>
  </CardContent>
  <CardFooter>
    <Button variant="ghost" size="sm">Cancel</Button>
    <Button size="sm">Save</Button>
  </CardFooter>
</Card>

Variants

The Card supports multiple visual variants:

Outlined (Default)

Standard card with a border.
<Card variant="outlined">
  <CardHeader>
    <CardTitle>Outlined Card</CardTitle>
    <CardDescription>This card has a border outline</CardDescription>
  </CardHeader>
  <CardContent>
    <p>Content for the outlined card variant.</p>
  </CardContent>
</Card>

Elevated

Card with shadow elevation and no border.
<Card variant="elevated">
  <CardHeader>
    <CardTitle>Elevated Card</CardTitle>
    <CardDescription>This card has a shadow elevation</CardDescription>
  </CardHeader>
  <CardContent>
    <p>Content for the elevated card variant with shadow.</p>
  </CardContent>
</Card>

Interactive

Card with hover effects and cursor pointer for clickable cards.
<Card variant="interactive">
  <CardHeader>
    <CardTitle>Interactive Card</CardTitle>
    <CardDescription>This card responds to hover and click</CardDescription>
  </CardHeader>
  <CardContent>
    <p>Hover over this card to see the interactive effects.</p>
  </CardContent>
</Card>

Sizes

Three size options control padding throughout the card:
{/* Small - Compact spacing */}
<Card size="sm">
  <CardHeader>
    <CardTitle>Small Card</CardTitle>
  </CardHeader>
  <CardContent>
    <p>Less padding for tight spaces.</p>
  </CardContent>
</Card>

{/* Medium - Default size */}
<Card size="md">
  <CardHeader>
    <CardTitle>Medium Card</CardTitle>
  </CardHeader>
  <CardContent>
    <p>Standard padding for most use cases.</p>
  </CardContent>
</Card>

{/* Large - Generous spacing */}
<Card size="lg">
  <CardHeader>
    <CardTitle>Large Card</CardTitle>
  </CardHeader>
  <CardContent>
    <p>More padding for comfortable reading.</p>
  </CardContent>
</Card>

Props

Card

variant
string
default:"outlined"
Visual style variant: "default" | "outlined" | "elevated" | "interactive"
size
string
default:"md"
Size of the card affecting padding: "sm" | "md" | "lg"
className
string
Additional CSS classes to apply

CardHeader

fullWidth
boolean
default:false
When true, expands header to card edges (useful for images)
size
string
Override the card’s size for this header: "sm" | "md" | "lg"
className
string
Additional CSS classes to apply

CardTitle

as
string
default:"h3"
HTML heading element: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
className
string
Additional CSS classes to apply

CardDescription

className
string
Additional CSS classes to apply

CardContent

className
string
Additional CSS classes to apply

CardFooter

size
string
Override the card’s size for this footer: "sm" | "md" | "lg"
className
string
Additional CSS classes to apply

Advanced Examples

Form Card

<Card>
  <CardHeader>
    <CardTitle>Sign In</CardTitle>
    <CardDescription>
      Enter your credentials to access your account
    </CardDescription>
  </CardHeader>
  <CardContent className="space-y-4">
    <Input
      label="Email"
      type="email"
      placeholder="Enter your email"
    />
    <Input
      label="Password"
      type="password"
      placeholder="Enter your password"
    />
  </CardContent>
  <CardFooter>
    <Button variant="ghost" className="flex-1">
      Forgot password?
    </Button>
    <Button className="flex-1">Sign In</Button>
  </CardFooter>
</Card>

Product Card

<Card>
  <CardHeader fullWidth>
    <div className="aspect-video bg-gradient-to-br from-blue-400 to-purple-500 rounded-t-lg" />
  </CardHeader>
  <CardHeader>
    <div className="flex justify-between items-start">
      <div>
        <CardTitle as="h2">Amazing Product</CardTitle>
        <CardDescription>
          High-quality product with excellent features
        </CardDescription>
      </div>
      <Button variant="ghost" size="sm">
        <MoreHorizontal size={16} />
      </Button>
    </div>
  </CardHeader>
  <CardContent>
    <div className="flex items-center gap-2">
      <div className="flex">
        {[...Array(5)].map((_, i) => (
          <Star key={i} size={16} className="fill-yellow-400 text-yellow-400" />
        ))}
      </div>
      <span className="text-sm [color:var(--text-secondary)]">
        (128 reviews)
      </span>
    </div>
  </CardContent>
  <CardFooter>
    <div className="flex items-center gap-2 flex-1">
      <span className="text-2xl font-bold [color:var(--text-primary)]">
        $99
      </span>
      <span className="text-sm [color:var(--text-tertiary)] line-through">
        $129
      </span>
    </div>
    <div className="flex gap-2">
      <Button variant="ghost" size="sm">
        <Heart size={16} />
      </Button>
      <Button size="sm">Add to Cart</Button>
    </div>
  </CardFooter>
</Card>

Event Card

<Card variant="interactive">
  <CardHeader>
    <CardTitle>Design Conference 2024</CardTitle>
    <CardDescription>
      Join us for an amazing conference about modern design
    </CardDescription>
  </CardHeader>
  <CardContent>
    <div className="space-y-3">
      <div className="flex items-center gap-2">
        <Calendar size={16} className="[color:var(--text-secondary)]" />
        <span className="[color:var(--text-secondary)]">
          March 15-17, 2024
        </span>
      </div>
      <div className="flex items-center gap-2">
        <MapPin size={16} className="[color:var(--text-secondary)]" />
        <span className="[color:var(--text-secondary)]">
          San Francisco, CA
        </span>
      </div>
    </div>
  </CardContent>
  <CardFooter>
    <div className="flex items-center gap-2 flex-1">
      <span className="text-lg font-semibold [color:var(--text-primary)]">
        $299
      </span>
      <span className="text-sm [color:var(--text-tertiary)]">
        Early bird
      </span>
    </div>
    <Button>Register Now</Button>
  </CardFooter>
</Card>

Design Tokens

The Card component uses semantic design tokens for consistent theming:
  • --card-radius: Border radius
  • --card-padding-sm/md/lg: Size-specific padding
  • --bg-primary: Card background
  • --border-primary: Card border color
  • --text-primary/secondary: Text colors
  • --shadow-md/lg: Elevation shadows
  • --transition-normal: Animation duration

Implementation Details

Context Management

The Card component uses React Context to share size and footer state between sub-components:
  • Size is automatically inherited by CardHeader and CardFooter
  • CardFooter automatically updates the Card’s padding when rendered
  • This ensures proper spacing without manual prop passing

Full-Width Headers

Use the fullWidth prop on CardHeader to create edge-to-edge headers (perfect for images):
<Card>
  <CardHeader fullWidth>
    <img src="/image.jpg" alt="Header image" className="rounded-t-lg" />
  </CardHeader>
  {/* Rest of card */}
</Card>
The CardFooter uses justify-between by default, making it perfect for action buttons:
<CardFooter>
  <Button variant="ghost">Cancel</Button>
  <Button>Save</Button>
</CardFooter>

Accessibility

  • Use semantic heading levels with the as prop on CardTitle
  • Ensure sufficient color contrast for text content
  • Add appropriate ARIA labels to interactive cards
  • Use CardDescription to provide context for screen readers

Best Practices

  1. Use consistent sizing: Keep card sizes consistent within the same layout
  2. Interactive variant for clickable cards: Use variant="interactive" when the entire card is clickable
  3. Proper heading hierarchy: Set appropriate heading levels with the as prop
  4. Avoid nesting cards: Cards should not be nested within other cards
  5. Footer for actions: Use CardFooter for primary actions related to the card content

Build docs developers (and LLMs) love