GitHub

Card

A flexible container for grouping related content. Uses a compound component pattern — compose Header, Content, and Footer independently for full layout control.

Compound PatternDark Mode3 VariantsComposable
01

Theme Preview

A profile card rendered with forced light and dark styling — independent of the current app theme.

Light
AJ

Alice Johnson

alice@example.com

AdminActive
Dark
AJ

Alice Johnson

alice@example.com

AdminActive
02

Live Demo

Variant
AJ

Alice Johnson

alice@example.com

AdminActive
trending_up
+12.5%

8,249

Monthly visitors

notifications

New comment

Bob left a reply on your post.

03

Code Snippet

src/ui/Card.tsx
import { Card } from "@/ui/Card";
import { Button } from "@/ui/Button";

<Card.Root variant="elevated">
  <Card.Header>
    <Card.Title>Account Settings</Card.Title>
    <Card.Description>
      Manage your profile and preferences.
    </Card.Description>
  </Card.Header>
  <Card.Content>
    <p className="text-sm text-slate-600 dark:text-slate-400">
      Your account was last updated 3 days ago.
    </p>
  </Card.Content>
  <Card.Footer>
    <Button size="sm">Save changes</Button>
    <Button variant="ghost" size="sm">Cancel</Button>
  </Card.Footer>
</Card.Root>

Flat exports seperti CardHeader, CardContent, dan lainnya tetap didukung untuk migrasi bertahap.

04

Copy-Paste (Single File)

Snippet ini self-contained dan bisa dipindahkan ke project lain tanpa setup alias atau util tambahan.

Card.tsx
import type { HTMLAttributes } from "react";

type ClassValue = string | false | null | undefined;
const cn = (...classes: ClassValue[]) => classes.filter(Boolean).join(" ");

export type CardVariant = "default" | "elevated" | "flat";

export interface CardProps extends HTMLAttributes<HTMLDivElement> {
  variant?: CardVariant;
}

const CARD_VARIANT_CLASSES: Record<CardVariant, string> = {
  default: "rounded-xl border border-slate-200 bg-white",
  elevated: "rounded-xl border border-slate-200 bg-white shadow-lg shadow-slate-200/60",
  flat: "rounded-xl border border-transparent bg-slate-50",
};

function CardRoot({ variant = "default", className, children, ...props }: CardProps) {
  return (
    <div className={cn(CARD_VARIANT_CLASSES[variant], className)} {...props}>
      {children}
    </div>
  );
}

function CardHeader({ className, children, ...props }: HTMLAttributes<HTMLDivElement>) {
  return <div className={cn("p-6 pb-4", className)} {...props}>{children}</div>;
}

function CardTitle({ className, children, ...props }: HTMLAttributes<HTMLHeadingElement>) {
  return <h3 className={cn("text-base font-bold text-slate-900", className)} {...props}>{children}</h3>;
}

function CardDescription({ className, children, ...props }: HTMLAttributes<HTMLParagraphElement>) {
  return <p className={cn("mt-1 text-sm text-slate-500", className)} {...props}>{children}</p>;
}

function CardContent({ className, children, ...props }: HTMLAttributes<HTMLDivElement>) {
  return <div className={cn("px-6 pb-4", className)} {...props}>{children}</div>;
}

function CardFooter({ className, children, ...props }: HTMLAttributes<HTMLDivElement>) {
  return <div className={cn("px-6 pb-6 flex items-center gap-3", className)} {...props}>{children}</div>;
}

type CardCompound = typeof CardRoot & {
  Root: typeof CardRoot;
  Header: typeof CardHeader;
  Title: typeof CardTitle;
  Description: typeof CardDescription;
  Content: typeof CardContent;
  Footer: typeof CardFooter;
};

export const Card = Object.assign(CardRoot, {
  Root: CardRoot,
  Header: CardHeader,
  Title: CardTitle,
  Description: CardDescription,
  Content: CardContent,
  Footer: CardFooter,
}) as CardCompound;
05

Props

All sub-components extend their corresponding HTML element attributes.

ComponentPropTypeDefaultDescription
Card.Rootvariant"default" | "elevated" | "flat""default"Visual style — border only, shadow, or flat background.
Card.RootclassNamestringAdditional CSS classes.
Card.HeaderclassNamestringSpacing wrapper for title + description.
Card.TitleclassNamestringRenders as h3. Bold, dark text.
Card.DescriptionclassNamestringRenders as p. Muted, secondary text.
Card.ContentclassNamestringMain body area with horizontal padding.
Card.FooterclassNamestringAction row with flex + gap. Usually holds buttons.
react-principles