# Card

A panel with a border that groups the content and the actions of one topic.

```tsx
import { Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ferry-ui'

export default function CardHero() {
  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <div>
          <CardTitle>Payment method</CardTitle>
          <CardDescription>We charge this card on the first day of each month.</CardDescription>
        </div>
        <CardAction>
          <Button size="tiny">Replace</Button>
        </CardAction>
      </CardHeader>
      <CardContent className="text-sm">Card ending in 4242</CardContent>
      <CardFooter className="justify-between text-[13px] text-foreground-light">Next charge on Nov 1</CardFooter>
    </Card>
  )
}
```

## Usage guidelines

- **One topic for each card.** A card groups the content of one topic: a summary, a list, a short form.
- **Do not nest cards.** Put a card next to a card, not in a card.
- **Use a pattern when one fits.** For a settings form, use [Form Card](/docs/components/form-card). For a KPI, use [Metric Card](/docs/components/metric-card). For a list of entities, use [Resource Card](/docs/components/resource-card).

## Anatomy

Import the parts and put them together. Each part in the card is optional.

```tsx title="Anatomy"

<Card>
  <CardHeader>
    <div>
      <CardTitle />
      <CardDescription />
    </div>
    <CardAction />
  </CardHeader>
  <CardContent />
  <CardFooter />
</Card>
```

| Part | Role |
| --- | --- |
| `Card` | The panel with a border. |
| `CardHeader` | The top bar. It puts the title on the left and the action on the right. |
| `CardTitle` | The title of the card. |
| `CardDescription` | One short sentence about the card. |
| `CardAction` | Small controls on the right of the header. |
| `CardContent` | The body, with padding. |
| `CardFooter` | The bottom bar. It aligns its content to the right. |

Wrap `CardTitle` and `CardDescription` in a `<div>`. The description then shows below the title.

## Examples

### Content only

A card needs no header and no footer. `CardContent` alone gives a panel with padding.

```tsx
import { Card, CardContent } from 'ferry-ui'

export default function CardContentOnly() {
  return (
    <Card className="w-full max-w-md">
      <CardContent>
        <p className="text-sm text-foreground-light">
          API keys give your backend access to the workspace. Keep them out of your source code.
        </p>
      </CardContent>
    </Card>
  )
}
```

### Header action

`CardAction` holds small controls on the right of the header: a button, a menu or a [Badge](/docs/components/badge). The buttons of this demo have the `tiny` and `icon-tiny` sizes.

```tsx
import { Button, Card, CardAction, CardContent, CardHeader, CardTitle } from 'ferry-ui'
import { MoreHorizontal, Plus } from 'lucide-react'

export default function CardHeaderAction() {
  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardTitle>API keys</CardTitle>
        <CardAction>
          <Button size="tiny" icon={<Plus />}>
            New key
          </Button>
          <Button variant="ghost" size="icon-tiny" icon={<MoreHorizontal />} aria-label="More actions" />
        </CardAction>
      </CardHeader>
      <CardContent>
        <p className="text-sm text-foreground-light">Keys give programs access to your workspace.</p>
      </CardContent>
    </Card>
  )
}
```

### Footer

`CardFooter` aligns its content to the right. Add `justify-between` to put helper text on the left.

```tsx
import { Badge, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ferry-ui'
import { ArrowUpRight } from 'lucide-react'

export default function CardWithFooter() {
  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <div>
          <CardTitle>Usage this month</CardTitle>
          <CardDescription>The count starts again on October 1.</CardDescription>
        </div>
        <CardAction>
          <Badge variant="warning">82%</Badge>
        </CardAction>
      </CardHeader>
      <CardContent>
        <div className="flex items-baseline gap-1">
          <span className="tabular text-2xl font-medium text-foreground">8,214</span>
          <span className="text-sm text-foreground-lighter">/ 10,000 requests</span>
        </div>
      </CardContent>
      <CardFooter className="justify-between">
        <span className="text-[13px] text-foreground-lighter">A higher plan has higher limits.</span>
        <Button iconRight={<ArrowUpRight />}>View plans</Button>
      </CardFooter>
    </Card>
  )
}
```

### Table in a card

Leave out `CardContent` for content that must touch the edges, such as a [Table](/docs/components/table). Remove the frame of the table with `containerClassName`.

```tsx
import {
  Button,
  Card,
  CardAction,
  CardHeader,
  CardTitle,
  StatusBadge,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
  type StatusTone,
} from 'ferry-ui'

const ORDERS: { id: string; customer: string; total: string; tone: StatusTone; status: string }[] = [
  { id: '#1042', customer: 'Northwind Traders', total: '$1,240.00', tone: 'success', status: 'Paid' },
  { id: '#1043', customer: 'Acme', total: '$860.50', tone: 'info', status: 'Pending' },
  { id: '#1044', customer: 'Globex', total: '$3,105.00', tone: 'neutral', status: 'Refunded' },
]

export default function CardWithTable() {
  return (
    <Card className="mx-auto w-full max-w-xl">
      <CardHeader>
        <CardTitle>Recent orders</CardTitle>
        <CardAction>
          <Button size="tiny">View all</Button>
        </CardAction>
      </CardHeader>
      {/* No CardContent: the table touches the edges. Its own frame is off. */}
      <Table aria-label="Recent orders" containerClassName="rounded-none border-0 shadow-none">
        <TableHeader>
          <TableRow className="hover:bg-transparent">
            <TableHead>Order</TableHead>
            <TableHead>Customer</TableHead>
            <TableHead>Status</TableHead>
            <TableHead className="text-right">Total</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {ORDERS.map((order) => (
            <TableRow key={order.id}>
              <TableCell className="font-mono text-[13px]">{order.id}</TableCell>
              <TableCell>{order.customer}</TableCell>
              <TableCell>
                <StatusBadge tone={order.tone} label={order.status} />
              </TableCell>
              <TableCell className="text-right tabular">{order.total}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </Card>
  )
}
```

### Long title

A long title wraps by default. To keep it on one line, give `min-w-0` to its wrapper and `truncate` to `CardTitle`.

```tsx
import { Button, Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from 'ferry-ui'

export default function CardLongTitle() {
  return (
    <Card className="w-full max-w-xs">
      <CardHeader>
        {/* min-w-0 lets the title block get smaller than its text. */}
        <div className="min-w-0">
          <CardTitle className="truncate">Plan for the customer support and the sales teams</CardTitle>
          <CardDescription className="truncate">Last edit by Maya Chen, three days ago</CardDescription>
        </div>
        <CardAction>
          <Button size="tiny">Open</Button>
        </CardAction>
      </CardHeader>
      <CardContent>
        <p className="text-sm text-foreground-light">The text of the body wraps on more than one line.</p>
      </CardContent>
    </Card>
  )
}
```

### Loading state

While the content loads, show a [Skeleton](/docs/components/skeleton) for each line. Set `aria-busy` on the card.

```tsx
import { Card, CardContent, CardHeader, Skeleton } from 'ferry-ui'

export default function CardLoading() {
  return (
    <Card className="w-full max-w-md" aria-busy="true">
      <CardHeader>
        <div className="flex flex-col gap-1.5">
          <Skeleton className="h-4 w-32" />
          <Skeleton className="h-3.5 w-48" />
        </div>
      </CardHeader>
      <CardContent className="flex flex-col gap-2">
        <Skeleton className="h-3.5 w-full" />
        <Skeleton className="h-3.5 w-full" />
        <Skeleton className="h-3.5 w-2/3" />
      </CardContent>
    </Card>
  )
}
```

## Accessibility

- `CardTitle` is a `<div>`. If the outline of the page needs a heading, put an `<h2>` or an `<h3>` in it.

## API reference

Each part accepts the attributes of the `<div>` element.

### Card

`Card` has no props of its own. It accepts the attributes of the element it renders.

### CardHeader

`CardHeader` has no props of its own. It accepts the attributes of the element it renders.

### CardTitle

`CardTitle` has no props of its own. It accepts the attributes of the element it renders.

### CardDescription

`CardDescription` has no props of its own. It accepts the attributes of the element it renders.

### CardAction

`CardAction` has no props of its own. It accepts the attributes of the element it renders.

### CardContent

`CardContent` has no props of its own. It accepts the attributes of the element it renders.

### CardFooter

`CardFooter` has no props of its own. It accepts the attributes of the element it renders.
