---
title: Card
description: A structured surface with header, content, footer, and actions.
group: Components
order: 22
---

Group forms, summaries, or media on a bordered surface. Compose the sections you need and add `CardAction` for a control in the header.

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

export function Example() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Welcome back</CardTitle>
        <CardDescription>
          Sign in with your work email to continue.
        </CardDescription>
      </CardHeader>
      <CardContent>
        <form className="flex flex-col gap-4">
          <div className="flex flex-col gap-1.5">
            <label htmlFor="email" className="text-sm">
              Email
            </label>
            <input
              id="email"
              type="email"
              placeholder="you@company.com"
              className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm outline-none"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <div className="flex items-center justify-between gap-2">
              <label htmlFor="password" className="text-sm">
                Password
              </label>
              <Button variant="link" className="text-xs text-muted-foreground">
                Forgot password?
              </Button>
            </div>
            <input
              id="password"
              type="password"
              className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm outline-none"
            />
          </div>
        </form>
      </CardContent>
      <CardFooter className="flex-col gap-3">
        <Button className="w-full">Continue</Button>
        <div className="flex w-full items-center gap-3 text-xs text-muted-foreground">
          <span className="h-px flex-1 bg-border" />
          or
          <span className="h-px flex-1 bg-border" />
        </div>
        <Button variant="outline" className="w-full">
          Continue with Google
        </Button>
      </CardFooter>
    </Card>
  )
}
```

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add card
```

## Usage

```tsx
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
```

```tsx
<Card>
  <CardHeader>
    <CardTitle>Project overview</CardTitle>
    <CardDescription>Last synced 2 hours ago</CardDescription>
    <CardAction>Edit</CardAction>
  </CardHeader>
  <CardContent>
    <p>12 components published in this namespace.</p>
  </CardContent>
  <CardFooter>
    <p>Updated Jul 30</p>
  </CardFooter>
</Card>
```

## Composition

```tree
Card
├── CardHeader
│   ├── CardTitle
│   ├── CardDescription
│   └── CardAction
├── CardContent
└── CardFooter
```

## Size

Pass `size="sm"` when the card needs tighter spacing.

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

export function Example() {
  return (
    <Card size="sm" className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Scheduled reports</CardTitle>
        <CardDescription>
          Weekly snapshots. No more manual exports.
        </CardDescription>
      </CardHeader>
      <CardContent>
        <ul className="list-disc space-y-1 pl-4 text-sm text-muted-foreground">
          <li>Choose a schedule (daily, or weekly).</li>
          <li>Send to channels or specific teammates.</li>
          <li>Include charts, tables, and key metrics.</li>
        </ul>
      </CardContent>
      <CardFooter className="flex-col gap-2">
        <Button size="sm" className="w-full">
          Set up scheduled reports
        </Button>
        <Button size="sm" variant="outline" className="w-full">
          See what&apos;s new
          <ChevronRightIcon data-icon="inline-end" />
        </Button>
      </CardFooter>
    </Card>
  )
}
```

## Spacing

Insets and section gaps use `--card-spacing`. Override it on the card to adjust the density in one place.

```tsx
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"

export function Example() {
  return (
    <Card className="w-full max-w-sm [--card-spacing:1.5rem]">
      <CardHeader>
        <CardTitle>Card Title</CardTitle>
      </CardHeader>
      <CardContent>Content uses the shared spacing token.</CardContent>
    </Card>
  )
}
```

Use `-mx-[var(--card-spacing)]` to extend content to the card edges while keeping the surrounding inset aligned. If the block sits above a footer, add `-mb-[var(--card-spacing)]` to `CardContent` to remove the section gap.

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

export function Example() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Terms of Service</CardTitle>
        <CardDescription>
          Review the terms before accepting the agreement.
        </CardDescription>
      </CardHeader>
      <CardContent className="-mx-[var(--card-spacing)] -mb-[var(--card-spacing)] border-y border-border p-0">
        <div className="show-scrollbar max-h-40 space-y-3 overflow-y-auto px-[var(--card-spacing)] py-3 text-sm text-muted-foreground">
          <p>These terms govern your use of the workspace…</p>
          <p>You are responsible for the content you upload…</p>
          <p>We may update features or limits as the service evolves…</p>
        </div>
      </CardContent>
      <CardFooter className="justify-end gap-2">
        <Button variant="outline">Decline</Button>
        <Button>Accept</Button>
      </CardFooter>
    </Card>
  )
}
```

## Image

Place media as the first child of `Card`, then add `pt-0` so it sits flush with the top edge.

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

export function Example() {
  return (
    <Card className="relative w-full max-w-sm pt-0">
      <img
        src="/assets/brand/demos/card-cover.png"
        alt="Event cover"
        className="aspect-video w-full object-cover"
      />
      <Badge className="absolute top-3 right-3">Featured</Badge>
      <CardHeader>
        <CardTitle>Design systems meetup</CardTitle>
        <CardDescription>
          A practical talk on component APIs, accessibility, and shipping
          faster.
        </CardDescription>
      </CardHeader>
      <CardFooter>
        <Button className="w-full">View Event</Button>
      </CardFooter>
    </Card>
  )
}
```

## Action

Put a header control in `CardAction` to align it to the top-right.

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

export function Example() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Create an account</CardTitle>
        <CardDescription>
          Enter your email to get started with the workspace.
        </CardDescription>
        <CardAction>
          <Button variant="ghost" size="sm">
            Sign in
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent>
        <form className="flex flex-col gap-4">
          <div className="flex flex-col gap-1.5">
            <label htmlFor="name" className="text-sm">
              Name
            </label>
            <input
              id="name"
              type="text"
              placeholder="Alex Rivera"
              className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm outline-none"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <label htmlFor="email" className="text-sm">
              Email
            </label>
            <input
              id="email"
              type="email"
              placeholder="you@company.com"
              className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm outline-none"
            />
          </div>
        </form>
      </CardContent>
      <CardFooter>
        <Button className="w-full">Create account</Button>
      </CardFooter>
    </Card>
  )
}
```

## Split

Set the card to `flex-row` for a side-by-side media layout.

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

export function Example() {
  return (
    <Card className="w-full max-w-md flex-row gap-0 py-0">
      <img
        src="/assets/brand/demos/card-cover.png"
        alt=""
        className="w-28 shrink-0 object-cover sm:w-36"
      />
      <div className="flex min-w-0 flex-1 flex-col gap-[var(--card-spacing)] py-[var(--card-spacing)]">
        <CardHeader>
          <CardTitle>Coastal workshop</CardTitle>
          <CardDescription>
            A half-day session on shipping design systems with less friction.
          </CardDescription>
        </CardHeader>
        <CardFooter>
          <Button size="sm">Register</Button>
        </CardFooter>
      </div>
    </Card>
  )
}
```

## Empty

A dashed border and `bg-transparent` keep an empty state visually light.

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

export function Example() {
  return (
    <Card className="w-full max-w-sm border-dashed bg-transparent">
      <CardHeader>
        <CardTitle>No projects yet</CardTitle>
        <CardDescription>
          Create a project to start collecting feedback and shipping updates.
        </CardDescription>
      </CardHeader>
      <CardContent className="flex justify-end">
        <Button size="sm">
          Create project
        </Button>
      </CardContent>
    </Card>
  )
}
```

## Danger

Pair destructive footer actions with direct copy that explains the consequence.

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

export function Example() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Delete workspace</CardTitle>
        <CardDescription>
          This permanently removes projects, members, and billing history. This
          cannot be undone.
        </CardDescription>
      </CardHeader>
      <CardFooter className="justify-end gap-2">
        <Button variant="outline" size="sm">Cancel</Button>
        <Button variant="destructive" size="sm">Delete workspace</Button>
      </CardFooter>
    </Card>
  )
}
```

## API Reference

### Card

| Prop | Type | Default |
| --- | --- | --- |
| size | "default" \| "sm" | "default" |
| className | string | — |

```tsx
<Card>
  <CardHeader>
    <CardTitle>Title</CardTitle>
    <CardDescription>Subtitle</CardDescription>
  </CardHeader>
  <CardContent>Body</CardContent>
</Card>
```

### CardHeader

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<CardHeader>
  <CardTitle>Title</CardTitle>
  <CardDescription>Subtitle</CardDescription>
</CardHeader>
```

### CardTitle

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<CardTitle>Title</CardTitle>
```

### CardDescription

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<CardDescription>Subtitle</CardDescription>
```

### CardAction

Places content in the top-right corner of the header.

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<CardHeader>
  <CardTitle>Title</CardTitle>
  <CardAction>
    <Button size="sm" variant="outline">Edit</Button>
  </CardAction>
</CardHeader>
```

### CardContent

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<CardContent>Body</CardContent>
```

### CardFooter

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<CardFooter>
  <Button>Save</Button>
</CardFooter>
```

## Source

```tsx
import type { HTMLAttributes } from "react";
import { cn } from "@/lib/utils";

type CardSize = "default" | "sm";

type CardProps = HTMLAttributes<HTMLDivElement> & {
  size?: CardSize;
};

export function Card({
  className,
  size = "default",
  ...props
}: CardProps) {
  return (
    <div
      data-slot="card"
      data-size={size}
      className={cn(
        "group/card flex flex-col gap-[var(--card-spacing)] overflow-hidden rounded-md border border-border bg-card py-[var(--card-spacing)] text-sm text-card-foreground",
        "[--card-spacing:1rem] data-[size=sm]:[--card-spacing:0.75rem]",
        "has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0",
        "[&>img:first-child]:rounded-t-md [&>img:last-child]:rounded-b-md",
        "[&.flex-row>img:first-child]:rounded-none [&.flex-row>img:first-child]:rounded-l-md",
        "[&.flex-row>img:last-child]:rounded-none [&.flex-row>img:last-child]:rounded-r-md",
        className,
      )}
      {...props}
    />
  );
}

export function CardHeader({
  className,
  ...props
}: HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      data-slot="card-header"
      className={cn(
        "@container/card-header grid auto-rows-min items-start gap-1 rounded-t-md px-[var(--card-spacing)]",
        "has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-action]:items-center has-data-[slot=card-description]:grid-rows-[auto_auto]",
        "has-data-[slot=card-action]:[&_[data-slot=card-description]]:col-start-1 has-data-[slot=card-action]:[&_[data-slot=card-description]]:row-start-2",
        "[.border-b]:pb-[var(--card-spacing)]",
        className,
      )}
      {...props}
    />
  );
}

export function CardTitle({
  className,
  ...props
}: HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      data-slot="card-title"
      className={cn(
        "font-normal leading-none tracking-wide text-card-foreground",
        className,
      )}
      {...props}
    />
  );
}

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

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

export function CardContent({
  className,
  ...props
}: HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      data-slot="card-content"
      className={cn("px-[var(--card-spacing)]", className)}
      {...props}
    />
  );
}

export function CardFooter({
  className,
  ...props
}: HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      data-slot="card-footer"
      className={cn(
        "flex items-center px-[var(--card-spacing)] py-[var(--card-spacing)] [.border-t]:pt-[var(--card-spacing)]",
        className,
      )}
      {...props}
    />
  );
}
```
