Card
Card: anatomy, keyboard behavior, state, and theming in Askr.
Example
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>Published props
Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.
CardActionProps
Import from @askrjs/themes/card.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
CardContentProps
Import from @askrjs/themes/card.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
CardDescriptionProps
Import from @askrjs/themes/card.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLParagraphElement>; | — | — |
CardFooterProps
Import from @askrjs/themes/card.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
CardHeaderProps
Import from @askrjs/themes/card.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
CardProps
Import from @askrjs/themes/card.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
variant | variant?: CardVariant | undefined; | — | — |
CardTitleProps
Import from @askrjs/themes/card.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLHeadingElement>; | — | — |
titleAs | titleAs?: CardTitleHeadingTag | undefined; | — | Choose the level that follows the surrounding document hierarchy. |
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 divs. 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.