# Card

> A surface container for grouped content. Compose freely inside — heading, text, button, an optional media block.

URL: https://apogee.avaui.dev/docs/card
Last updated: 2026-10-16

A surface container for grouped content. Compose freely inside — heading, text, button, an optional media block.

Last updated October 16, 2026

```astro
<Card>
<Heading as="h3">Card title</Heading>
<Text color="neutral-faded">A short supporting description of what this card links to or explains.</Text>
</Card>
```

## Add

```sh
npx @ava-ui/apogee add card
```

No Apogee dependencies. Commonly composed with `Heading`, `Text`, `Button`, and `Image`.

## Usage

```astro
---
import Card from '@/components/apogee/Card/Card.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
---
<Card>
  <Heading as="h3">Card title</Heading>
  <p>Body copy.</p>
</Card>
```

## Anatomy

Card is a single component with named slots for its regions, not a Card.Header/Card.Body family:

-   **Root** — the `<a>` (when `href` is set) or `<div>` surface: border, radius, background. Any content you pass goes directly inside its padded body, in the default slot.
-   **`slot="media"`** (optional) — renders at the top, above the body, inset 4px with matching corners, clipped to a 16:9 (`aspect-video`) box; with `mediaPosition="start"` it becomes a fixed-width column beside the body from `sm`. Meant for an `<img>` — the slot content is sized to fill it (`size-full object-cover`).
-   **`slot="header"`** (optional) — a bordered row above the body, stacked on phones and a row from `md`, for a title, a badge, actions.
-   **`slot="footer"`** (optional) — a bordered row below the body for links or buttons; put `class="ms-auto"` on the group that should sit at the end.

## API

`@composable` — exposed to Content Editors in AVA. Any other native `<a>` / `<div>` attribute (e.g. `target`, `rel`, `aria-label`) passes through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `href` | `string` | — | Renders the card as an `<a>` and makes the whole surface a link (hover/focus states apply). Omit for a static `<div>`. |
| `as` | `'div' \| 'article' \| 'section' \| 'li' \| 'button'` | `'div'` | Tag to render; `button` makes the whole card a button (ignored when `href` is set). |
| `raised` | `boolean` | `false` | Adds the raised shadow. |
| `selected` | `boolean` | `false` | Primary border, for a chosen card in a set. |
| `padding` | `'none' \| 'small' \| 'medium' \| 'large'` | `'medium'` | Body padding; header and footer rows use the same horizontal step with a shorter vertical one. With `none` (a flush table or image) the regions keep the medium step. |
| `mediaPosition` | `'top' \| 'start'` | `'top'` | Where the `media` slot sits: above the body, or a 15rem column beside it from `sm`. |
| `class` | `string` | — | Extra classes, merged last. |

## Variants

The only structural variant is presence of the `media` slot:

```astro
<Card>
<img slot="media" src="https://picsum.photos/seed/apogee/640/360" alt="" />
<Heading as="h3">With media</Heading>
<Text color="neutral-faded" size="small">An image slotted above the body, inset 4px from the card's edges.</Text>
</Card>
<Card>
<Heading as="h3">Without media</Heading>
<Text color="neutral-faded" size="small">Just the padded body.</Text>
</Card>
```

Header and footer regions:

```astro
<Card>
<div slot="header">
  <Heading as="h3">Team members</Heading>
  <Text color="neutral-faded" size="small">Manage who has access.</Text>
</div>
<Button slot="header" size="small" variant="outline" color="neutral">Invite</Button>
<Text size="small">Body…</Text>
<Button slot="footer" variant="link" class="ms-auto">View all</Button>
</Card>
```

Media beside the body (`mediaPosition="start"`), stacked on phones:

```astro
<Card mediaPosition="start">
<img slot="media" src="…" alt="" />
<Heading as="h3">Side media</Heading>
<Text color="neutral-faded" size="small">A fixed-width image column from sm.</Text>
</Card>
```

Every `padding`:

```astro
<Card padding="none">…</Card>
<Card padding="small">…</Card>
<Card padding="medium">…</Card>
<Card padding="large">…</Card>
```

A raised card and a selected card:

```astro
<Card raised>…</Card>
<Card selected>…</Card>
```

An article card and a button card:

```astro
<Card as="article"><h3>Post title</h3><p>Summary</p></Card>
<Card as="button" selected>Pick this plan</Card>
```

## States

Interactive states only apply when `href` is set (the whole card is a link):

-   **Hover** — background shifts to `--background-neutral-faded-highlighted`.
-   **Focus** — a visible `focus-visible` ring (`--outline-color-focus`).
-   **Static** (no `href`) — no hover/focus treatment; it’s a plain container.

```astro
<Card href="#">
<Heading as="h3">Clickable card</Heading>
<Text color="neutral-faded" size="small">Hover or focus to see the interactive state.</Text>
</Card>
```

## Examples

As a link card with media and a call to action:

```astro
<Card href="#">
<img slot="media" src="https://picsum.photos/seed/apogee2/640/360" alt="" />
<Heading as="h3">Ship faster with Apogee</Heading>
<Text color="neutral-faded" size="small">Copy-in, accessible Astro components.</Text>
</Card>
```

Composed with a Button (card itself is static — the button is the click target):

```astro
<Card>
<Heading as="h3">Upgrade your plan</Heading>
<Text color="neutral-faded" size="small">Unlock more seats and priority support.</Text>
<Button class="mt-4">Upgrade</Button>
</Card>
```

A card header with a title, a count, actions and a kebab menu (the kebab is absolute on phones so the title keeps the first line):

```astro
<Card>
<div slot="header" class="relative flex min-w-0 flex-1 flex-col gap-1 md:pe-0 pe-10">
  <div class="flex items-center gap-2">
    <Heading as="h3">Invoices</Heading>
    <Badge size="small" variant="outline" color="neutral">24</Badge>
  </div>
  <Text color="neutral-faded" size="small">Everything billed this quarter.</Text>
</div>
<div slot="header" class="flex items-center gap-2">
  <Button size="small" variant="outline" color="neutral">Export</Button>
  <Button size="small">New invoice</Button>
  <Dropdown id="card-kebab" label="More actions" placement="bottom-end">
    <Button slot="anchor" popovertarget="card-kebab" size="small" variant="ghost" color="neutral" iconOnly aria-label="More"><EllipsisVertical /></Button>
    <DropdownItem>Archive all</DropdownItem>
  </Dropdown>
</div>
…
</Card>
```

## Accessibility

-   With `href`, the whole surface is a real `<a>` — keyboard-focusable, `Enter` navigates, no `div` click-handler faked.
-   Without `href`, it’s a plain `<div>` — don’t add a click handler to fake navigation; use `href` instead so it stays keyboard-operable.
-   If a linked card also contains its own interactive children (a Button), avoid nesting a second link/button _inside_ an `href` card — nested interactive controls are invalid HTML and confuse both mouse and keyboard users. Keep linked cards content-only, or keep the card static and put the single action inside.
-   `media` images need real `alt` text (or `alt=""` if purely decorative).

## Tokens

| Token | Used for |
| --- | --- |
| `--border-neutral` / `--border-primary` | Card outline / `selected` outline |
| `--background-elevation-raised` / `--foreground-neutral` | Card background / default text color |
| `--background-neutral-faded-highlighted` | Hover background (linked cards only) |
| `--outline-color-focus` | Focus ring (linked cards only) |
| `--border-neutral` | Header and footer rules |
| `--radius-lg` | Corner radius |
| `--shadow-surface` / `--shadow-raised` | Resting edge (a skin hook) / `raised` lift |

## Do / Don’t

-   **Do** use `href` when the whole card should be one click target — don’t bury the only action in a nested link.
-   **Do** keep card content short — a heading, a line or two, maybe one action.
-   **Don’t** nest another link or button inside an `href` card.
-   **Don’t** mix unrelated content types in a grid of cards — keep each card’s shape consistent within a set.

## Content

-   Title is a short noun phrase (`Heading`, 2–6 words), not a full sentence.
-   Supporting text is one to two lines — truncate or link to “Read more” rather than letting a card grow tall in a grid.

## Related

[

BoxThe layout primitive. Direction, gap, padding, surface, border, radius and shadow as token props.

](https://apogee.avaui.dev/docs/box)[

ActionableThe bare interactive primitive. A button, link or focusable div with the focus ring and disabled handling, nothing else.

](https://apogee.avaui.dev/docs/actionable)[

SectionHeaderEyebrow, heading and description at the top of a section or an app page, with actions, a divider and the level, alignment and colours every section shares.

](https://apogee.avaui.dev/docs/section-header)
