# Card

A generic content surface with shallow depth. It holds content and is not meant to stand in for your app's own signature objects.

![A raised surface with a header (title and description), content and a footer of actions.](https://ui.terratcg.app/previews/card/anatomy/ios-light.png)

```tsx
import { Button, ButtonText, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Text } from "@terratcg/ui";
import { View } from "react-native";

export default function CardAnatomy() {
  return (
    <View style={{ width: 320 }}>
      <Card>
        <CardHeader>
          <CardTitle>Weekend trip</CardTitle>
          <CardDescription>Saturday at the Camargue, back by evening.</CardDescription>
        </CardHeader>
        <CardContent>
          <Text>Bring binoculars: the flamingos feed far from the path.</Text>
        </CardContent>
        <CardFooter>
          <Button size="sm">
            <ButtonText>Join</ButtonText>
          </Button>
          <Button size="sm" variant="ghost">
            <ButtonText>Later</ButtonText>
          </Button>
        </CardFooter>
      </Card>
    </View>
  );
}
```

## Import

```tsx
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@terratcg/ui";
```

## Usage

```tsx
<Card>
  <CardHeader>
    <CardTitle>Weekend trip</CardTitle>
    <CardDescription>Saturday at the Camargue.</CardDescription>
  </CardHeader>
  <CardContent>
    <Text>Bring binoculars.</Text>
  </CardContent>
  <CardFooter>
    <Button size="sm">
      <ButtonText>Join</ButtonText>
    </Button>
  </CardFooter>
</Card>
```

CardHeader pads its title and description, CardContent and CardFooter continue under it with the same side padding, and CardFooter lays its actions in a row. A Card inside another Card (or inside any TerraUI surface) takes a shallower lift on its own.

## Examples

### Raised, nested and well

A raised Card lifts off the page; a Card inside it takes the shallower nested lift; a well is cut into the page for quiet, empty content.

![A raised Card lifts off the page; a Card inside it takes the shallower nested lift; a well is cut into the page for quiet, empty content.](https://ui.terratcg.app/previews/card/frames/ios-light.png)

```tsx
import { Card, CardDescription, CardHeader, CardTitle } from "@terratcg/ui";
import { View } from "react-native";

export default function CardFrames() {
  return (
    <View style={{ width: 320, gap: 16 }}>
      <Card>
        <CardHeader>
          <CardTitle>Raised</CardTitle>
          <CardDescription>The default frame.</CardDescription>
        </CardHeader>
        <View style={{ paddingHorizontal: 16, paddingBottom: 16 }}>
          <Card>
            <CardHeader>
              <CardTitle>Nested</CardTitle>
              <CardDescription>A Card on a Card lifts less.</CardDescription>
            </CardHeader>
          </Card>
        </View>
      </Card>
      <Card frame="well">
        <CardHeader>
          <CardTitle>Well</CardTitle>
          <CardDescription>Nothing saved yet.</CardDescription>
        </CardHeader>
      </Card>
    </View>
  );
}
```

### Danger tone

tone="danger" turns the Card into an error notice: a danger border on the soft danger floor. Here the text keeps the default ink, which reads better on that floor.

![tone="danger" turns the Card into an error notice: a danger border on the soft danger floor. Here the text keeps the default ink, which reads better on that floor.](https://ui.terratcg.app/previews/card/danger/ios-light.png)

```tsx
import { Card, CardDescription, CardHeader, CardTitle } from "@terratcg/ui";
import { View } from "react-native";

export default function CardDanger() {
  return (
    <View style={{ width: 320 }}>
      <Card tone="danger">
        <CardHeader>
          <CardTitle tone="default">Upload failed</CardTitle>
          <CardDescription tone="default">The photo did not reach the server. Check your connection and try again.</CardDescription>
        </CardHeader>
      </Card>
    </View>
  );
}
```

### Selectable

selectable turns the border into a 2 pt ring, clear until selected: choosing never moves the layout.

![selectable turns the border into a 2 pt ring, clear until selected: choosing never moves the layout.](https://ui.terratcg.app/previews/card/selectable/ios-light.png)

```tsx
import { Card, CardDescription, CardHeader, CardTitle } from "@terratcg/ui";
import { useState } from "react";
import { Pressable, View } from "react-native";

const PLANS = [
  { id: "monthly", title: "Monthly", description: "Billed every month." },
  { id: "yearly", title: "Yearly", description: "Two months free." },
];

export default function CardSelectable() {
  const [plan, setPlan] = useState("yearly");
  return (
    <View style={{ width: 320, gap: 12 }} accessibilityRole="radiogroup">
      {PLANS.map(({ id, title, description }) => (
        <Pressable
          key={id}
          accessibilityRole="radio"
          accessibilityState={{ checked: plan === id }}
          onPress={() => setPlan(id)}
        >
          <Card selectable selected={plan === id}>
            <CardHeader>
              <CardTitle>{title}</CardTitle>
              <CardDescription>{description}</CardDescription>
            </CardHeader>
          </Card>
        </Pressable>
      ))}
    </View>
  );
}
```

## Props

### Card

### CardProps

| Prop           | Type                      | Description                                                                                                                                                                                        |
| -------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `style?`       | `StyleProp<SurfaceStyle>` |                                                                                                                                                                                                    |
| `tone?`        | `"danger" \| "default"`   | "danger" presents an error notice with the danger palette. Default: `"default"`                                                                                                                    |
| `nested?`      | `boolean`                 | Force the shallower lift of a shell resting on another shell, even on the bare page: a quieter surface beneath a full Card. Default: `false`                                                       |
| `translucent?` | `boolean`                 | A see-through floor (72% of its colour), for a surface resting inside a living shell: its ground shows through, the rims and shadows stay. Default: `false`                                        |
| `ground?`      | `ReactNode`               | A layer painted on the shell's floor, under its rims, sheen and content, clipped to the shell (a game surface's living sky). Pointer events pass through it.                                       |
| `radius?`      | `number`                  | Corner radius, `theme.shape.radiusLg` by default; half the side for a round key.                                                                                                                   |
| `clip?`        | `boolean`                 | Cut the children to the Card's shape (a stage light reaching its edge). The Card then carries its material itself rather than on rasterized layers, which its own clip would cut. Default: `false` |
| `frame?`       | `"raised" \| "well"`      | Raised by default; "well" recesses it below the page.                                                                                                                                              |
| `borderless?`  | `boolean`                 | No border: the surface's edge is its rims alone (a stage). A well has no border either way.                                                                                                        |
| `selectable?`  | `boolean`                 | A Card the user can choose: its border becomes a 2 pt ring, clear until `selected`, so choosing never moves the layout.                                                                            |
| `selected?`    | `boolean`                 | Chosen: an accent ring on the accent floor.                                                                                                                                                        |

Card also accepts React Native's `View` props. Its frame props combine in four ways only, and TypeScript holds you to them:

- `frame="raised"` (the default), bordered.
- Raised and `selectable`, with `selected`: the border becomes a 2 pt ring, clear until selected, so choosing never moves the layout. Selected, the ring turns accent over the accent-soft floor.
- Raised and `borderless`: the edge is the rims alone, for a stage.
- `frame="well"`: recessed below the page, for quiet, empty or placeholder content. A well has no border, and can't be selectable.

`ground` paints a layer on the Card's floor, under its rims, sheen and content, clipped to its shape; touches pass through it. `clip` cuts the children to the Card's shape. `radius` replaces the theme's large radius.

### Parts

CardHeader, CardContent and CardFooter take React Native's `View` props. CardTitle (title type) and CardDescription (body type, secondary) take React Native's `Text` props and Text's `tone` and `weight`; in a `danger` Card both default to the danger tone.

## Accessibility

- **Role:** none. A Card groups content visually; the controls in it carry the roles.
- **Selectable:** a selectable Card is not pressable by itself. Wrap it in a `Pressable` with the role that fits, such as `radio` in a `radiogroup`, and its `accessibilityState`, as the Selectable example does.
- **Title:** CardTitle is not announced as a header. Pass `accessibilityRole="header"` when the Card heads a section of the page.
- **Danger contrast:** in Electric the danger ink on the soft danger floor reaches 4.5:1, AA for body text with no room to spare. A theme that changes `danger` or `dangerSoft` should check that pair, or give the title and description `tone="default"`, as the Danger example does.

## Platform differences

- **iOS and Android:** the same surfaces, depths and rings.
- **Web:** Card renders through react-native-web for these docs, but the web is not a supported target.

## Rules

- Use a Card for content, not for a control: a pressable surface wants a Button, a Row or a Cell.
- Don't use a Card for your app's own signature objects (a trading card, a ticket): draw those yourself.
- Keep one level of nesting. A Card on a Card lifts less; a third level reads as noise.
- Choose the frame by meaning: `raised` for content, `well` for what is empty or waiting.
- Set `translucent` only on a surface resting inside a living layer, such as a Card's `ground`, never on the bare page: the floor keeps 72% of its colour so the layer shows through, while the rims and shadows stay.
