# Card

A titled section with an optional description and one action, around a body the caller writes.

A card groups one subject: a heading, a line that explains it and at most one action at the end of its head. The body is written by a closure into the same string, so any part can sit inside. Use cards to separate subjects, not to frame every element.

## Use it

Web:

```rust
use ocean_ui_web::Card;
Card { title: "Plan", description: None, action: None }.render(&mut out, |out| out.push_str("…"))
```

Native:

```rust
use ocean_ui_core::{Button, Size, Variant};
use ocean_ui_native::containers::Card;
let new = Button::new("New", Variant::Outline).size(Size::Sm);
Card::new("Environments").description("Where each branch runs.").action(new).show(ui, |ui| ui.label("…"))
```

Cargo feature `base`; since 2026-10-29.
In App UI the `card` block draws it ([card](/ui/blocks/card.md)).

## Props

| Name | Type | Default | What |
|---|---|---|---|
| `title` | `&str` | `—` | The card's heading, written as an h2. |
| `description` | `Option<&str>` | `None` | One line under the title. |
| `action` | `Option<&dyn Html>` | `None` | One part at the end of the head, usually a button or a menu. |

On the Mac: ![Card, dark](/ui/native/card-dark.png) ![Card, light](/ui/native/card-light.png)

## Examples

### Title and body

A card groups one thing: a head over its content.

```rust
Card { title: "Acme renewal", description: Some("Due in 12 days"), action: None }.render(
    out,
    |out| { Status { word: "On track".into(), tone: Tone::Ok }.html(out); },
);
```

### With an action

One action in the head, a link or a quiet button.

```rust
let open = Button::new("Open", Variant::Ghost).size(Size::Sm).href("#card");
Card { title: "Import", description: None, action: Some(&open) }.render(
    out,
    |out| {
        Progress { label: "Rows".into(), value: 640.0, max: 1000.0 }.html(out);,
    },
);
```

### Holding parts

Any part goes inside: badges, figures, a list.

```rust
Card { title: "Plan", description: Some("Team, billed yearly"), action: None }.render(
    out,
    |out| {
        Badge { text: "Current".into(), tone: Tone::Brand }.html(out);
        out.push_str("<p>Twelve seats, unlimited apps.</p>");
    },
);
```

## Accessibility

- The card is a section headed by an h2, so it takes its place in the outline under the page head's h1.
- The action keeps its own role and name; the card adds none.

## Related

- [Page head](/ui/components/page-head.md)
- [Button](/ui/components/button.md)
- [Menu](/ui/components/menu.md)
