# Card

A raised surface with a header, content and footer.

Source: [card.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/card.tsx)

```tsx
import { ChevronRightIcon } from "lucide-react";

import { Avatar, AvatarFallback, AvatarGroup, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress";
import { people } from "@/examples/data";

export default function CardProject() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Website redesign</CardTitle>
        <CardDescription>Updated 2 hours ago by Sam Rivera</CardDescription>
        <CardAction>
          <Badge variant="info">In progress</Badge>
        </CardAction>
      </CardHeader>
      <CardContent className="flex flex-col gap-4">
        <p className="text-muted-foreground">
          Refresh the marketing site with the new brand and rebuild the pricing page.
        </p>
        <Progress value={64}>
          <ProgressLabel>18 of 28 tasks</ProgressLabel>
          <ProgressValue />
        </Progress>
      </CardContent>
      <CardFooter>
        <AvatarGroup>
          {people.slice(0, 4).map((person) => (
            <Avatar key={person.value} size="sm">
              <AvatarImage src={person.image} alt={person.name} />
              <AvatarFallback>{person.initials}</AvatarFallback>
            </Avatar>
          ))}
        </AvatarGroup>
        <Button variant="ghost" size="sm" className="ml-auto">
          Open
          <ChevronRightIcon />
        </Button>
      </CardFooter>
    </Card>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

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

## Usage

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

export function ProjectCard() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Website redesign</CardTitle>
        <CardDescription>Updated 2 hours ago</CardDescription>
        <CardAction>
          <Button variant="ghost" size="sm">
            Edit
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent>Refresh the marketing site with the new brand.</CardContent>
      <CardFooter>
        <Button variant="secondary" size="sm">
          Open
        </Button>
      </CardFooter>
    </Card>
  );
}
```

Every part is optional. `CardAction` sits at the top right of the header.

## Composition

- `Card`
  - `CardHeader`
    - `CardTitle`
    - `CardDescription`
    - `CardAction`
  - `CardContent`
  - `CardFooter`

## Examples

### Form

Add `border-b` to the header to divide it from the content.

```tsx
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

const roles = { viewer: "Viewer", member: "Member", admin: "Admin" };

export default function CardForm() {
  return (
    <Card className="w-full max-w-sm">
      <CardHeader className="border-b">
        <CardTitle>Invite a teammate</CardTitle>
        <CardDescription>They’ll get an email with a link to join Acme.</CardDescription>
      </CardHeader>
      <CardContent>
        <FieldGroup className="gap-3">
          <Field>
            <FieldLabel htmlFor="invite-email">Email</FieldLabel>
            <Input id="invite-email" type="email" placeholder="lena@example.com" />
          </Field>
          <Field>
            <FieldLabel htmlFor="invite-role">Role</FieldLabel>
            <Select items={roles} defaultValue="member">
              <SelectTrigger id="invite-role" className="w-full">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {Object.entries(roles).map(([value, label]) => (
                  <SelectItem key={value} value={value}>
                    {label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </Field>
        </FieldGroup>
      </CardContent>
      <CardFooter className="justify-end">
        <Button variant="ghost" size="sm">
          Cancel
        </Button>
        <Button variant="primary" size="sm">
          Send invite
        </Button>
      </CardFooter>
    </Card>
  );
}
```

### Small

`size="sm"` tightens the spacing.

```tsx
import { EllipsisIcon } from "lucide-react";

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

export default function CardSmall() {
  return (
    <Card size="sm" className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Plan usage</CardTitle>
        <CardDescription>Resets on April 1</CardDescription>
        <CardAction>
          <Button variant="ghost" size="icon-sm" aria-label="More options">
            <EllipsisIcon />
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent className="flex flex-col gap-2">
        <div className="flex items-center justify-between">
          <span className="text-muted-foreground">Storage</span>
          <span className="tabular-nums">6.4 of 10 GB</span>
        </div>
        <div className="flex items-center justify-between">
          <span className="text-muted-foreground">Members</span>
          <span className="tabular-nums">12 of 25</span>
        </div>
      </CardContent>
    </Card>
  );
}
```

### Flat

`variant="flat"` swaps the shadow for a border.

```tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress";

export default function CardFlat() {
  return (
    <Card variant="flat" className="w-full max-w-sm">
      <CardHeader>
        <CardTitle>Usage this month</CardTitle>
        <CardDescription>Across all projects in Acme</CardDescription>
      </CardHeader>
      <CardContent className="flex flex-col gap-4">
        <Progress value={42}>
          <ProgressLabel>Requests</ProgressLabel>
          <ProgressValue />
        </Progress>
        <Progress value={12}>
          <ProgressLabel>Storage</ProgressLabel>
          <ProgressValue />
        </Progress>
      </CardContent>
    </Card>
  );
}
```

### Cover image

An image as the card's first child runs to its edges. Give a decorative image an empty `alt`.

```tsx
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Status } from "@/components/ui/status";
import { gradientImage } from "@/examples/data";

export default function CardCoverImage() {
  return (
    <Card className="w-full max-w-sm">
      <img
        src={gradientImage("#c3d4fd", "#b8e9c1", 320, 120)}
        alt=""
        className="aspect-[8/3] w-full object-cover"
      />
      <CardHeader>
        <CardTitle>Mobile app</CardTitle>
        <CardDescription>The iOS and Android apps, built from one codebase.</CardDescription>
      </CardHeader>
      <CardFooter>
        <Status variant="success">Released</Status>
        <span className="ml-auto text-xs text-faint">v3.1.0</span>
      </CardFooter>
    </Card>
  );
}
```

## Props

| Part | Extends |
| --- | --- |
| `Card` | `<div>` |
| `CardHeader` | `<div>` |
| `CardTitle` | `<div>` |
| `CardDescription` | `<div>` |
| `CardAction` | `<div>` |
| `CardContent` | `<div>` |
| `CardFooter` | `<div>` |

### Card

| Prop       | Type                  | Description                                                                                            |
| ---------- | --------------------- | ------------------------------------------------------------------------------------------------------ |
| `size?`    | `"default" \| "sm"`   | Default: `"default"`                                                                                   |
| `variant?` | `"default" \| "flat"` | `flat` swaps the raised shadow for a hairline border, for panels set into a page. Default: `"default"` |
