<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Card

A surface that groups one thing's content: a media slot across the top, a title with a description and an action, a body, and a footer. With `href` the whole card is one link, named by its title, with one tab stop, and its action and footer controls stay their own.

```tsx
import { Card } from "@/components/ouro/card/card";
```

## Example

```tsx
<Card title="Pantry" description="What you have on hand" action={<Button variant="plain">Edit</Button>}>
  <Text>Flour, rice, black beans.</Text>
</Card>
<Card
  href="/cookbooks/weeknight"
  media={<AspectRatio ratio={4 / 3}><img src={cover} alt="" /></AspectRatio>}
  title="Weeknight dinners"
  description="12 recipes"
/>
```

## Use instead when

- Use `Item` instead for a compact row in a list (media, title, description and actions side by side), and `Stack` when the group needs no surface.

## Props

Type: `CardProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `action` | `ReactNode` |  | One control beside the title, at the end: a menu or a share button. With `href` it stays its own tab stop, above the link. |
| `as` | `"div" \| "article" \| "li" \| "section"` | `"div"` | The element to render: `li` in a list of cards, `article` for a self-contained item. |
| `children` | `ReactNode` |  | The card's body, under the title. |
| `className` | `string` |  | Layout only: margins, width, grid placement (`col-span-2`). Appearance classes warn in development; use `variant`. |
| `description` | `ReactNode` |  | A sentence or a count under the title: "12 recipes". |
| `external` | `boolean` |  | With `href`: opens the link in a new tab, safely, and tells screen readers so. |
| `footer` | `ReactNode` |  | A row at the bottom for buttons or a timestamp. With `href`, controls here stay their own tab stops. |
| `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `3` | The title's heading level. Pick the level below the section the card sits in. |
| `href` | `string` |  | Makes the whole card one link, with one tab stop, named by its `title`. Internal paths go through `OuroProvider`'s `linkComponent`, as with `Link`. Controls in any slot (a share button over the `media`, a link in the body, a button in `action` or `footer`) stay their own tab stops and take their own clicks and taps. |
| `linkRef` | `Ref<HTMLAnchorElement>` |  | With `href`: a ref to the link's `<a>`, to move focus to it (as a "Show more" does for the first new card). |
| `media` | `ReactNode` |  | An image or video across the top, edge to edge: usually an `AspectRatio` around an `<img>`, so the card keeps its shape while the image loads. |
| `ref` | `Ref<HTMLElement>` |  | Reaches the rendered element. |
| `size` | `"sm" \| "md"` | `"md"` | Padding: `md` is 24px, `sm` 16px for dense grids. |
| `stackBelow` | `"sm" \| "md"` |  | Below this breakpoint the `action` drops under the title and description, as in a settings panel on a phone. Without it the action stays beside the title at every width. |
| `title` | `ReactNode` |  | The card's title, as a heading. With `href` it is the link's text, so a linked card needs one. |
| `variant` | `"outline" \| "plain"` | `"outline"` | `outline` is a bordered surface in the theme's card colors; `plain` has no frame or padding, for a card's structure inside a surface of your own. |

## Native attributes

Also accepts the attributes in React's `HTMLAttributes` (`id`, `aria-*`, event handlers), passed to the rendered element, except those named on this page.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `actions`: Not a prop. Ouro UI uses `action` beside the title, or `footer` below the body.
- `asChild`: Not a prop. With `href` the whole card is a link: `<Card href="/recipes/1" title="…">`.
- `header`: Not a prop. The header is `title`, `description` and `action`.
- `image`: Not a prop. Ouro UI uses `media`: `<Card media={<AspectRatio ratio={4 / 3}><img … /></AspectRatio>}>`.
- `subtitle`: Not a prop. Ouro UI uses `description`: `<Card description="12 recipes">`.
- `to`: Not a prop. Ouro UI uses `href`, for router links too: `<Card href="/recipes/1" title="…">`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: Card merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

[^layout-classes]: test:registry/ouro/components/ouro/card/card.test.tsx::Card > passes attributes and a ref through, and lets layout classes win verify:testExists(registry/ouro/components/ouro/card/card.test.tsx::Card > passes attributes and a ref through, and lets layout classes win)
