# Card

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/card/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

A surface that holds one block of a screen together: a head with its title and actions, and a body (also called a panel or tile). Reach for it for each block of a dashboard or a detail view.

```ts
import { Card, CardHeader, CardBody } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/card/

The line under the head is not the normal case; whitespace carries the hierarchy. Set it where the body runs edge to edge, a table or a log, together with a flush body.

A collapsible card turns its head into the button for its body, with the state in the card or, controlled, in your application.

## Examples

### Head and body

A `CardHeader` names the block, a `CardBody` holds whatever belongs to it; the card knows nothing of its content.

```tsx
import { Card, CardBody, CardHeader, Text } from "@umriss-ui/core";

export default function HeadAndBody() {
  return (
    <Card style={{ maxWidth: 480 }}>
      <CardHeader title="Checkout" />
      <CardBody>
        <Text size="sm" tone="secondary">
          Payments team · tier 1 · 95th percentile below 300 ms, 99.95 % available a month.
        </Text>
      </CardBody>
    </Card>
  );
}
```

### Eyebrow and actions

An `eyebrow` names the kind of thing above the title; `actions` stand on the right, a badge or a button or both.

```tsx
import { Badge, Button, Card, CardBody, CardHeader, Stack, Text } from "@umriss-ui/core";

export default function EyebrowAndActions() {
  return (
    <Card style={{ maxWidth: 520 }}>
      <CardHeader
        eyebrow="Invoice"
        title="INV-26-0318 · Brandlow Office Supply"
        actions={
          <Stack direction="row" gap={2} align="center">
            <Badge tone="warning">Awaiting approval</Badge>
            <Button size="sm" variant="primary">
              Approve
            </Button>
          </Stack>
        }
      />
      <CardBody>
        <Text size="sm" tone="secondary">
          Facilities, CC-4400 · received 16 March, due 15 April · 2,321.98 € gross.
        </Text>
      </CardBody>
    </Card>
  );
}
```

### A flush body

Where the body runs edge to edge, like a log or a table, set `flush` on the body and `divider` on the head.

```tsx
import { Card, CardBody, CardHeader } from "@umriss-ui/core";

const LOG = `09:42:10  OPENED        INC-1048 by the latency alert on Checkout
09:46:02  ACKNOWLEDGED  Jonas Keller
09:58:37  NOTE          Card payments time out at the provider; retries doubled the load
10:11:05  MITIGATED     Retries capped at one, p95 back below 300 ms`;

export default function AFlushBody() {
  return (
    <Card style={{ maxWidth: 640 }}>
      <CardHeader divider eyebrow="INC-1048" title="Timeline" />
      <CardBody flush>
        <pre
          style={{
            margin: 0,
            padding: "var(--u-space-4)",
            overflowX: "auto",
            fontFamily: "var(--u-font-mono)",
            fontSize: "var(--u-text-xs)",
            lineHeight: 1.7,
            color: "var(--u-color-text-secondary)",
          }}
        >
          {LOG}
        </pre>
      </CardBody>
    </Card>
  );
}
```

### Fold the body

Set `collapsible` and the head becomes the button for the body; `defaultCollapsed` starts it folded.

```tsx
import { Badge, Card, CardBody, CardHeader, Stack, Text } from "@umriss-ui/core";

const LATE = [
  { stop: "Stop 4 · Marlow & Finch Books", window: "08:00–10:00", arrival: "10:35" },
  { stop: "Stop 9 · Ashcombe Dental", window: "10:00–12:00", arrival: "12:40" },
];

export default function FoldTheBody() {
  return (
    <Card collapsible defaultCollapsed style={{ maxWidth: 520 }}>
      <CardHeader eyebrow="Tour T-01" title="Late stops" actions={<Badge pill>{LATE.length}</Badge>} />
      <CardBody>
        <Stack gap={2}>
          {LATE.map((late) => (
            <Text key={late.stop} size="sm">
              {late.stop} - window {late.window}, planned {late.arrival}
            </Text>
          ))}
        </Stack>
      </CardBody>
    </Card>
  );
}
```

### Fold from outside

Control `collapsed` with `onCollapsedChange` when a toolbar folds every card at once, or an address keeps which stand open.

```tsx
import { useState } from "react";
import { Button, Card, CardBody, CardHeader, Stack, Text } from "@umriss-ui/core";

const PEOPLE = [
  { id: "arjun", name: "Arjun Mehta", work: "Member portal: sign-in form, 16 h left" },
  { id: "chloe", name: "Chloe Durand", work: "Member portal: profile page, 10 h left" },
  { id: "noah", name: "Noah Fischer", work: "Online shop: search results design, 6 h left" },
];

export default function FoldFromOutside() {
  const [folded, setFolded] = useState<ReadonlySet<string>>(() => new Set(["noah"]));
  const fold = (id: string, collapsed: boolean) =>
    setFolded((previous) => {
      const next = new Set(previous);
      if (collapsed) next.add(id);
      else next.delete(id);
      return next;
    });

  return (
    <Stack gap={3} style={{ maxWidth: 520 }}>
      <Stack direction="row" gap={2}>
        <Button size="sm" onClick={() => setFolded(new Set())}>
          Open all
        </Button>
        <Button size="sm" onClick={() => setFolded(new Set(PEOPLE.map((p) => p.id)))}>
          Fold all
        </Button>
      </Stack>
      {PEOPLE.map((person) => (
        <Card
          key={person.id}
          collapsible
          collapsed={folded.has(person.id)}
          onCollapsedChange={(collapsed) => fold(person.id, collapsed)}
        >
          <CardHeader eyebrow="Sprint 14" title={person.name} />
          <CardBody>
            <Text size="sm">{person.work}</Text>
          </CardBody>
        </Card>
      ))}
    </Stack>
  );
}
```

### Cards on a dashboard

Each block of a dashboard is a card in a grid; a card with nothing to show says so with an `EmptyState`.

```tsx
import { Badge, Button, Card, CardBody, CardHeader, EmptyState, Grid, Stack, Text } from "@umriss-ui/core";

const INCIDENTS = [
  { id: "INC-1048", title: "Checkout slow, card payments time out", severity: "SEV1" },
  { id: "INC-1047", title: "Webhook deliveries delayed", severity: "SEV3" },
];

export default function CardsOnADashboard() {
  return (
    <Grid minItemWidth="240px" gap={4}>
      <Card>
        <CardHeader title="Open incidents" actions={<Badge tone="danger">{INCIDENTS.length}</Badge>} />
        <CardBody>
          <Stack gap={3}>
            {INCIDENTS.map((incident) => (
              <Stack key={incident.id} gap={1}>
                <Text size="xs" tone="muted">
                  {incident.id} · {incident.severity}
                </Text>
                <Text size="sm">{incident.title}</Text>
              </Stack>
            ))}
          </Stack>
        </CardBody>
      </Card>
      <Card>
        <CardHeader title="On call" />
        <CardBody>
          <Stack gap={2}>
            <Text size="sm">Primary: Jonas Keller</Text>
            <Text size="sm">Secondary: Ada Mwangi</Text>
            <Text size="xs" tone="muted">
              Handover tomorrow at 09:00
            </Text>
          </Stack>
        </CardBody>
      </Card>
      <Card>
        <CardHeader title="Snoozed alerts" />
        <CardBody>
          <EmptyState
            title="Nothing snoozed"
            description="Alerts you snooze wait here until they wake."
            action={<Button size="sm">Open the alert list</Button>}
          />
        </CardBody>
      </Card>
    </Grid>
  );
}
```

## When to use something else

- Many sections of one long content, opened one or several at a time → Accordion
- A line between two groups without a surface → Divider

## Keyboard

| Key | Action |
|---|---|
| `Tab` | Reaches the actions in the head and, in a collapsible card, its Show or Hide button; a folded body is inert and passed over. |
| `Enter / Space` | On Show or Hide, unfolds or folds the body. |

The keys of [Button](https://romanhaendler.github.io/umriss-ui/core/button/#keyboard-button) apply here.

## API

### `CardProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `collapsible` | `boolean` | `false` | Allows collapsing and expanding the body through the header.<br>Shown in: [Fold the body](https://romanhaendler.github.io/umriss-ui/core/card/#fold-the-body), [Fold from outside](https://romanhaendler.github.io/umriss-ui/core/card/#fold-from-outside) |
| `defaultCollapsed` | `boolean` | `false` | Initial state of the collapse mechanism; afterwards it belongs to the card. Only effective together with `collapsible`.<br>Shown in: [Fold the body](https://romanhaendler.github.io/umriss-ui/core/card/#fold-the-body) |
| `collapsed` | `boolean` | — | Controlled: whether the body is folded away - for a card folded from outside, or one whose state is kept in an address. Only effective together with `collapsible`.<br>Shown in: [Fold from outside](https://romanhaendler.github.io/umriss-ui/core/card/#fold-from-outside) |
| `onCollapsedChange` | `(collapsed: boolean) => void` | — | Reports the wish of the header's button. Controlled, the card folds only once `collapsed` follows; uncontrolled it is merely a message.<br>Shown in: [Fold from outside](https://romanhaendler.github.io/umriss-ui/core/card/#fold-from-outside) |
| `ref` | `Ref<HTMLElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of the rendered element.

### `CardHeaderProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `title` *required* | `ReactNode` | — | The heading of the card. A node and not merely text, so that an identifier with markup can stand in it too.<br>Shown in: [Head and body](https://romanhaendler.github.io/umriss-ui/core/card/#head-and-body), [Eyebrow and actions](https://romanhaendler.github.io/umriss-ui/core/card/#eyebrow-and-actions), [A flush body](https://romanhaendler.github.io/umriss-ui/core/card/#a-flush-body) and 27 more |
| `eyebrow` | `ReactNode` | — | Small all-caps label above the title, e.g. a category.<br>Shown in: [Eyebrow and actions](https://romanhaendler.github.io/umriss-ui/core/card/#eyebrow-and-actions), [A flush body](https://romanhaendler.github.io/umriss-ui/core/card/#a-flush-body), [Fold the body](https://romanhaendler.github.io/umriss-ui/core/card/#fold-the-body) and 17 more |
| `actions` | `ReactNode` | — | Right-aligned area, e.g. buttons or badges.<br>Shown in: [Eyebrow and actions](https://romanhaendler.github.io/umriss-ui/core/card/#eyebrow-and-actions), [Fold the body](https://romanhaendler.github.io/umriss-ui/core/card/#fold-the-body), [Cards on a dashboard](https://romanhaendler.github.io/umriss-ui/core/card/#cards-on-a-dashboard) and 13 more |
| `divider` | `boolean` | `false` | A dividing line below the head. Only set it where the body needs it - typically with flush data (flush tables, logs). With normal content the white space carries the hierarchy on its own.<br>Shown in: [A flush body](https://romanhaendler.github.io/umriss-ui/core/card/#a-flush-body) |
| `ref` | `Ref<HTMLDivElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of `<div>` – without `title`.

### `CardBodyProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `flush` | `boolean` | `false` | Removes the inner padding, e.g. for flush tables.<br>Shown in: [A flush body](https://romanhaendler.github.io/umriss-ui/core/card/#a-flush-body) |
| `ref` | `Ref<HTMLDivElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of `<div>`.
