# Shadcn Card

> Free shadcn/ui card component for React — a styled container with header, title, description, content, footer, and a top-right CardAction slot. Login, stats, pricing, image, and action-menu cards. The examples official shadcn never spelled out.

Source: https://designrevision.com/components/card

The **Shadcn Card** is the container everything else lives in — a styled surface with a header, content, and footer, plus a top-right **`CardAction`** slot. It's the canonical <a href="https://ui.shadcn.com/docs/components/card" rel="nofollow">shadcn/ui</a> card, with **no dependency**, and the component you'll reuse most: dashboards, pricing, auth, and settings all sit inside one.

## The parts

Seven plain, styled divs:

- **`Card`** — the surface (`rounded-xl border bg-card shadow-sm`, `py-6`, `gap-6`).
- **`CardHeader`** — a grid holding the title, description, and an optional action.
- **`CardTitle`** / **`CardDescription`** — the heading and subtext.
- **`CardAction`** — a top-right slot in the header (see below).
- **`CardContent`** — the body (`px-6`); put anything here.
- **`CardFooter`** — actions or meta (`px-6`, flex).

## CardAction — the top-right slot

`CardHeader` is a grid that **automatically makes room** for a `CardAction` (`has-data-[slot=card-action]:grid-cols-[1fr_auto]`), so the title/description flow left while a button, [Dropdown Menu](/components/dropdown-menu), or [Badge](/components/badge) stays pinned top-right. The Card, Stat cards, Pricing, and Action-menu examples all use it.

## It's a container

A Card has no opinion about its content — it's a surface you compose inside. That's why the examples below are really a tour of the rest of the library:

- **Forms** — a login card with [Input](/components/input) + [Label](/components/label) + [Button](/components/button) (the Login form example).
- **Stats** — a `CardDescription` label over a big `CardTitle` number with a trend [Badge](/components/badge) (the Stat cards example).
- **Settings** — labelled [Switch](/components/switch) rows (the Settings card example).
- **Data** — a list of rows with avatars and amounts (the Recent activity example).

## Pricing & media

A **pricing card** is a Card with a "Most popular" badge in `CardAction`, the price under the description, a check-listed feature set, and a CTA footer (the Pricing card example). For a **media card**, add `pt-0` + `overflow-hidden` to the Card and drop an image as the first child so it bleeds to the rounded top edge (the Card with image example).

## Footers & dividers

`CardFooter` is `flex items-center` — use `justify-between` for a split, or `flex-col gap-2` for stacked full-width buttons. To add a **divider**, put `border-b` on the header or `border-t` on the footer; the primitive's `[.border-b]:pb-6` / `[.border-t]:pt-6` adjust the padding for you.

## Theming

The Card reads your design tokens — `--card` / `--card-foreground` (the surface), `--border`, `--muted-foreground` (descriptions) — so it follows your theme, including dark mode, with no overrides. Restyle a single card with a className: `border-primary bg-transparent shadow-none` for an outline look, or drop the border for a flat surface.

## Installation

### card

**Install:**

```bash
npx shadcn@latest add @designrevision/card
```

**Dependencies:** utils

**Usage & accessibility:** Seven styled container parts (Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter) — plain divs, no dependency. CardHeader is a grid that makes room for a top-right CardAction (button or menu) automatically via has-data-[slot=card-action]. Add [.border-b] to the header or [.border-t] to the footer to get the divider padding. Compose anything inside CardContent — it's a container, not a fixed layout.

```tsx
// components/ui/card.tsx
import * as React from "react"

import { cn } from "@/lib/utils"

function Card({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card"
      className={cn(
        "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
        className
      )}
      {...props}
    />
  )
}

function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-header"
      className={cn(
        "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
        className
      )}
      {...props}
    />
  )
}

function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-title"
      className={cn("leading-none font-semibold", className)}
      {...props}
    />
  )
}

function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-description"
      className={cn("text-muted-foreground text-sm", className)}
      {...props}
    />
  )
}

function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-action"
      className={cn(
        "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
        className
      )}
      {...props}
    />
  )
}

function CardContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-content"
      className={cn("px-6", className)}
      {...props}
    />
  )
}

function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-footer"
      className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
      {...props}
    />
  )
}

export {
  Card,
  CardHeader,
  CardFooter,
  CardTitle,
  CardAction,
  CardDescription,
  CardContent,
}
```

## Examples

### Card

Every part in one place — a header with a `CardAction`, a content form, and a footer.

**Install:**

```bash
npx shadcn@latest add @designrevision/card-demo-01
```

**Dependencies:** card, button, input, label

```tsx
// components/ui/card-demo-01.tsx
import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function CardDemo01() {
  return (
    <Card className="w-[360px]">
      <CardHeader>
        <CardTitle>Create project</CardTitle>
        <CardDescription>Deploy your new project in one click.</CardDescription>
        <CardAction>
          <Button variant="outline" size="sm">
            Import
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent className="grid gap-4">
        <div className="grid gap-2">
          <Label htmlFor="project">Name</Label>
          <Input id="project" placeholder="Name of your project" />
        </div>
        <div className="grid gap-2">
          <Label htmlFor="framework">Framework</Label>
          <Input id="framework" placeholder="Next.js" />
        </div>
      </CardContent>
      <CardFooter className="justify-between">
        <Button variant="ghost">Cancel</Button>
        <Button>Deploy</Button>
      </CardFooter>
    </Card>
  )
}
```

---

### Login form

A sign-in card with email/password fields, a forgot-password link, and primary + OAuth [buttons](/components/button).

**Install:**

```bash
npx shadcn@latest add @designrevision/card-login-01
```

**Dependencies:** card, button, input, label

```tsx
// components/ui/card-login-01.tsx
import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function CardLogin01() {
  return (
    <Card className="w-[360px]">
      <CardHeader>
        <CardTitle>Login to your account</CardTitle>
        <CardDescription>Enter your email below to sign in.</CardDescription>
      </CardHeader>
      <CardContent className="grid gap-4">
        <div className="grid gap-2">
          <Label htmlFor="email">Email</Label>
          <Input id="email" type="email" placeholder="m@example.com" />
        </div>
        <div className="grid gap-2">
          <div className="flex items-center justify-between">
            <Label htmlFor="password">Password</Label>
            <a
              href="#"
              className="text-sm underline-offset-4 hover:underline"
            >
              Forgot?
            </a>
          </div>
          <Input id="password" type="password" />
        </div>
      </CardContent>
      <CardFooter className="flex-col gap-2">
        <Button className="w-full">Sign in</Button>
        <Button variant="outline" className="w-full">
          Continue with Google
        </Button>
      </CardFooter>
    </Card>
  )
}
```

---

### Stat cards

A row of metric cards — a label, a big number, and a trend [Badge](/components/badge) in the `CardAction` slot.

**Install:**

```bash
npx shadcn@latest add @designrevision/card-stats-01
```

**Dependencies:** lucide-react, card, badge

```tsx
// components/ui/card-stats-01.tsx
import { TrendingDownIcon, TrendingUpIcon } from "lucide-react"

import { Badge } from "@/components/ui/badge"
import {
  Card,
  CardAction,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

const stats = [
  { label: "Total Revenue", value: "$45,231.89", change: "+20.1%", up: true },
  { label: "Subscriptions", value: "+2,350", change: "+180.1%", up: true },
  { label: "Churn rate", value: "1.2%", change: "-0.4%", up: false },
]

export default function CardStats01() {
  return (
    <div className="grid gap-4 sm:grid-cols-3">
      {stats.map((stat) => (
        <Card key={stat.label} className="gap-2">
          <CardHeader>
            <CardDescription>{stat.label}</CardDescription>
            <CardTitle className="text-2xl tabular-nums">{stat.value}</CardTitle>
            <CardAction>
              <Badge variant="secondary" className="gap-1">
                {stat.up ? (
                  <TrendingUpIcon className="size-3" />
                ) : (
                  <TrendingDownIcon className="size-3" />
                )}
                {stat.change}
              </Badge>
            </CardAction>
          </CardHeader>
        </Card>
      ))}
    </div>
  )
}
```

---

### Settings card

A notifications card with labelled [Switch](/components/switch) rows — the toggleable-settings panel.

**Install:**

```bash
npx shadcn@latest add @designrevision/card-notifications-01
```

**Dependencies:** card, label, switch

```tsx
// components/ui/card-notifications-01.tsx
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"

const settings = [
  { id: "comments", label: "Comments", desc: "When someone comments on your post.", checked: true },
  { id: "mentions", label: "Mentions", desc: "When someone @mentions you.", checked: true },
  { id: "follows", label: "Follows", desc: "When you gain a new follower.", checked: false },
]

export default function CardNotifications01() {
  return (
    <Card className="w-[400px]">
      <CardHeader>
        <CardTitle>Notifications</CardTitle>
        <CardDescription>
          Choose what you want to be notified about.
        </CardDescription>
      </CardHeader>
      <CardContent className="grid gap-5">
        {settings.map((setting) => (
          <div
            key={setting.id}
            className="flex items-center justify-between gap-4"
          >
            <div className="space-y-0.5">
              <Label htmlFor={setting.id}>{setting.label}</Label>
              <p className="text-muted-foreground text-xs">{setting.desc}</p>
            </div>
            <Switch id={setting.id} defaultChecked={setting.checked} />
          </div>
        ))}
      </CardContent>
    </Card>
  )
}
```

---

### Pricing card

A pricing tier — a "Most popular" [Badge](/components/badge), a price, a check-listed feature set, and a CTA.

**Install:**

```bash
npx shadcn@latest add @designrevision/card-pricing-01
```

**Dependencies:** lucide-react, card, badge, button

```tsx
// components/ui/card-pricing-01.tsx
import { CheckIcon } from "lucide-react"

import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

const features = [
  "Unlimited projects",
  "Advanced analytics",
  "24/7 priority support",
  "Custom domains",
  "Team collaboration",
]

export default function CardPricing01() {
  return (
    <Card className="w-[340px]">
      <CardHeader>
        <CardAction>
          <Badge>Most popular</Badge>
        </CardAction>
        <CardTitle>Pro</CardTitle>
        <CardDescription>For growing teams that need more.</CardDescription>
        <div className="pt-2">
          <span className="text-3xl font-bold">$29</span>
          <span className="text-muted-foreground"> / month</span>
        </div>
      </CardHeader>
      <CardContent>
        <ul className="grid gap-2 text-sm">
          {features.map((feature) => (
            <li key={feature} className="flex items-center gap-2">
              <CheckIcon className="text-primary size-4 shrink-0" />
              {feature}
            </li>
          ))}
        </ul>
      </CardContent>
      <CardFooter>
        <Button className="w-full">Get started</Button>
      </CardFooter>
    </Card>
  )
}
```

---

### Card with image

A media card whose image bleeds to the top edge via `pt-0` + `overflow-hidden`, with a price/CTA footer.

**Install:**

```bash
npx shadcn@latest add @designrevision/card-image-01
```

**Dependencies:** card, button

```tsx
// components/ui/card-image-01.tsx
import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export default function CardImage01() {
  return (
    <Card className="w-[340px] gap-0 overflow-hidden pt-0">
      {/* Swap this for an <img> or next/image. */}
      <div className="aspect-video bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500" />
      <CardHeader className="pt-6">
        <CardTitle>Mountain Retreat</CardTitle>
        <CardDescription>A quiet cabin in the Dolomites.</CardDescription>
      </CardHeader>
      <CardContent className="text-muted-foreground pt-2 text-sm">
        Two nights, breakfast included. Free cancellation up to 48 hours before
        check-in.
      </CardContent>
      <CardFooter className="justify-between pt-6">
        <span className="font-semibold">
          $240{" "}
          <span className="text-muted-foreground text-sm font-normal">
            / night
          </span>
        </span>
        <Button>Book now</Button>
      </CardFooter>
    </Card>
  )
}
```

---

### Action menu

A `CardAction` holding a "⋮" [Dropdown Menu](/components/dropdown-menu) in the top-right of the header.

**Install:**

```bash
npx shadcn@latest add @designrevision/card-action-01
```

**Dependencies:** lucide-react, card, button, dropdown-menu

```tsx
// components/ui/card-action-01.tsx
import {
  MoreVerticalIcon,
  PencilIcon,
  ShareIcon,
  Trash2Icon,
} from "lucide-react"

import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export default function CardAction01() {
  return (
    <Card className="w-[360px]">
      <CardHeader>
        <CardTitle>Design System</CardTitle>
        <CardDescription>Last edited 2 hours ago by Ada.</CardDescription>
        <CardAction>
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button
                variant="ghost"
                size="icon"
                className="size-8"
                aria-label="Open menu"
              >
                <MoreVerticalIcon />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              <DropdownMenuItem>
                <PencilIcon />
                Edit
              </DropdownMenuItem>
              <DropdownMenuItem>
                <ShareIcon />
                Share
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem variant="destructive">
                <Trash2Icon />
                Delete
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </CardAction>
      </CardHeader>
      <CardContent className="text-muted-foreground text-sm">
        A shared library of tokens, components, and guidelines for the product
        team.
      </CardContent>
    </Card>
  )
}
```

---

### Recent activity

A data card listing recent sales — an initials avatar, name/email, and a right-aligned amount.

**Install:**

```bash
npx shadcn@latest add @designrevision/card-data-01
```

**Dependencies:** card, avatar

```tsx
// components/ui/card-data-01.tsx
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

const sales = [
  { name: "Olivia Martin", email: "olivia@example.com", amount: "+$1,999.00", initials: "OM" },
  { name: "Jackson Lee", email: "jackson@example.com", amount: "+$39.00", initials: "JL" },
  { name: "Isabella Nguyen", email: "isabella@example.com", amount: "+$299.00", initials: "IN" },
  { name: "William Kim", email: "will@example.com", amount: "+$99.00", initials: "WK" },
]

export default function CardData01() {
  return (
    <Card className="w-[400px]">
      <CardHeader>
        <CardTitle>Recent sales</CardTitle>
        <CardDescription>You made 265 sales this month.</CardDescription>
      </CardHeader>
      <CardContent className="grid gap-5">
        {sales.map((sale) => (
          <div key={sale.email} className="flex items-center gap-3">
            <Avatar className="size-9">
              <AvatarFallback className="text-xs">{sale.initials}</AvatarFallback>
            </Avatar>
            <div className="grid gap-0.5">
              <p className="text-sm font-medium leading-none">{sale.name}</p>
              <p className="text-muted-foreground text-xs">{sale.email}</p>
            </div>
            <span className="ml-auto text-sm font-medium tabular-nums">
              {sale.amount}
            </span>
          </div>
        ))}
      </CardContent>
    </Card>
  )
}
```
