# Card

> Card: anatomy, keyboard behavior, state, and theming in Askr.

Source: [https://askrjs.com/docs/components/card](https://askrjs.com/docs/components/card)

Status: stable. Packages: @askrjs/themes/card.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

```tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@askrjs/themes/components';

<Card>
  <CardHeader><CardTitle>Project health</CardTitle><CardDescription>Updated just now</CardDescription></CardHeader>
  <CardContent><ProjectHealth /></CardContent>
</Card>
```

## Purpose

Grouping related content into a bounded, readable unit is what Card exists for — a stat, a settings section, a list row, anything that should visually register as one thing on the page. Behind it sits nothing but styling: no focus trap, no keyboard routing, no ARIA role to manage, so @askrjs/ui never had to define a headless Card primitive in the first place. What you get is a styled `<div>` plus a family of layout parts — `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`, `CardAction` — that hand you consistent spacing and typography instead of making you rebuild them by hand each time.

## Install and import

Card ships from `@askrjs/themes/card`, which also gets pulled in through the broader `@askrjs/themes/components` barrel if you're already importing from there. Pull in `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`, and `CardAction` as needed — you don't have to use every part on every card. There's nothing to install from `@askrjs/ui` for this one; `@askrjs/themes` is the whole story.

## Live examples

A minimal card is just `<Card><CardHeader><CardTitle>...</CardTitle></CardHeader><CardContent>...</CardContent></Card>` — three parts covers most real usage. Add `CardDescription` under the title for a subhead, and `CardFooter` for actions or metadata that should sit visually separate from the body. `CardAction` is meant for a control (a button, a menu trigger) placed inline with the header, like an overflow menu next to a title.

## Anatomy

The parts map directly to DOM elements: `Card` renders a `div`, `CardTitle` defaults to an `h3` (override the tag with `titleAs`, typed to `h1`-`h6`, when a card sits somewhere in the page's heading outline that isn't level 3), and `CardDescription` renders a `p` — the rest (`CardHeader`, `CardContent`, `CardFooter`, `CardAction`) are `div`s. Nesting is flat and predictable: header parts go inside `CardHeader`, and everything else is a direct child of `Card`. There's no required order beyond what makes visual sense — you can omit any part you don't need.

## State model

Nothing about a Card is tracked internally by the component itself. `variant`, typed as `CardVariant` (`"default" | "raised"`), is the one prop that changes how it looks, toggling between a flat surface and an elevated one. Conditions like loading, selected, or disabled aren't modeled here at all — you express those yourself through conditional rendering or a wrapping data attribute.

## Keyboard and accessibility

Because Card is static markup, there's no keyboard interaction to account for and nothing to manage with focus or ARIA — accessibility here is about what you put inside it, not the container itself. Use `CardTitle`'s heading semantics correctly: it defaults to `h3`, but override it with `titleAs` (`h1`-`h6`) so a card's title lines up with wherever it actually sits in the page's real heading outline, rather than leaving every card at `h3` regardless of context. If a card as a whole is interactive (say, a clickable card), wrap it in a real button or link rather than adding a click handler to the div.

## Styling and tokens

Card follows the same public contract as the rest of the theme package: style the `data-slot` hooks and `--ak-*` tokens, not internal structure. The `raised` variant is driven by the shared shadow tokens, so if you're adjusting elevation across the app, change the token rather than overriding `box-shadow` directly on individual cards. Spacing inside `CardHeader`, `CardContent`, and `CardFooter` comes from the general `--ak-space-*` scale (`gap`/`padding` set to `--ak-space-6`), not the separate `--ak-density-*` family — density tokens are scoped to form-control sizing (`--ak-density-control-height-*`/`-padding-x-*`) and aren't what Card's own layout reads from.

## API

`CardProps` extends native `div` props (minus `children` and `ref`, which are re-typed) with an optional `variant?: CardVariant` and forwards a `ref` to `HTMLDivElement`. `CardHeaderProps`, `CardContentProps`, and `CardFooterProps` are all plain `div`-prop wrappers with their own typed `ref`; `CardTitleProps` types its `ref` as `HTMLHeadingElement` and `CardDescriptionProps` as `HTMLParagraphElement`. `CardTitle` is the one part with a real prop of its own beyond `variant`-style styling knobs: `titleAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'`, defaulting to `'h3'`, for correcting the heading level in context.

## Edge cases

Because every part is just a styled native element, most "edge cases" are really just CSS: long titles will wrap by default rather than truncate, so add your own `text-overflow` handling if you need single-line titles. If a card's content can overflow (a long table or list inside `CardContent`), you're responsible for scroll containment — Card doesn't clip or scroll its children automatically. Empty cards render fine but will look sparse; pair with `EmptyState` from the same package if you need a real empty-content pattern.

## Related pages

For grouped clickable rows rather than a single content block, look at Item and ItemGroup, which are built for lists of interactive entries. If you need elevation and layout coordination at a bigger scale — panels, dashboard sections — Block and Container from `@askrjs/themes` compose well as a wrapper around several cards. Table and Data Table are the better fit once the content inside a card starts looking like rows and columns rather than free-form content.

## Documentation navigation

[Previous](https://askrjs.com/docs/components/scroll-area/index.md) | [Next](https://askrjs.com/docs/components/avatar-and-item/index.md)
