Askr
UI & Components

Card

Use card with the current published Askr packages.

  • @askrjs/themes/card0.0.13

How to use card

Card should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep card labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
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` renders an `h3`, 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: keep heading levels consistent with the rest of the page since it always renders as `h3`. 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 same density tokens used everywhere else in the theme, so cards stay visually consistent with buttons, inputs, and other surfaces without extra work.

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`. None of these parts take special props beyond `children` and standard native attributes — `variant` is the only thing that isn't just passthrough.

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.

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.