Card
Groups related content and actions in a titled container.
Installation
The CLI installs the Radix or Base UI file to match your project.
If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.
When to use
Use it
- To group content and actions about one thing under a title, like the contacts on an account or a summary of open cases.
- For a related list on a record page: a title with a count, a New button in the header and a View all link in the footer, as the Related list example shows.
- For dashboard tiles in a grid, where each card stands on its own.
- For a sub-section inside a card. Give the inner card
size="sm", as in Nested.
Don't use it
- For a record or page header with the record name, key fields and page actions. Use a plain
<header>. - To switch between peer panels of equal weight. Use Tabs.
- For page layout. Use plain
<section>or<div>elements with grid or flex, and put cards inside them. - As one big click target. Put a link in the title or a button in the header action instead.
- For a third level of nesting. Inside a small card, use a list, a heading or a divider.
Usage
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"Card
├── CardHeader
│ ├── CardTitle
│ ├── CardDescription
│ └── CardAction
├── CardContent
└── CardFooterEvery part is optional. CardAction places itself at the end of the header, beside the title and description.
Examples
Nested
Two cards with `size="sm"` inside another, which drop their border and shadow.
With icon
The contact object's icon before the title, an `Icon` inside `CardTitle` with `flex items-center gap-3`, and New in `CardAction`.
Empty and loading
A card with no tasks shows `Empty` in `CardContent`, and a card still loading its events shows a `Spinner` in its place.
API reference
| Part | Built on |
|---|---|
Card | <div> (opens in a new tab) |
CardHeader | <div> (opens in a new tab) |
CardTitle | <div> (opens in a new tab) |
CardDescription | <div> (opens in a new tab) |
CardAction | <div> (opens in a new tab) |
CardContent | <div> (opens in a new tab) |
CardFooter | <div> (opens in a new tab) |
Card
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "default" |
SLDS mapping
Cardisslds-card. A card inside a card drops its border and shadow, as.slds-card .slds-carddoes.CardHeaderisslds-card__header,CardTitleisslds-card__header-title, andCardActionis theslds-no-flexslot at the end of the header.CardDescriptionhas no SLDS counterpart. It uses SLDS's meta text (slds-text-body_small).CardContentisslds-card__body slds-card__body_inner, andCardFooterisslds-card__footer.
Known gaps:
- No header icon slot. Put the object icon inside CardTitle, as With icon shows.
- No linked title style (
slds-card__header-link). Put an<a>inside CardTitle and addhover:underline. - No margin between stacked sibling cards. Set a gap on the container.
- No
slds-card_boundary. To bring back a nested card's border, passin-data-[slot=card]:border-border. - Every part renders a
div, where SLDS uses<article>,<header>and<footer>.
| Prop | Value | SLDS | Notes |
|---|---|---|---|
size | default | slds-card | |
size | sm | slds-card slds-card | The nested tier: one radius step down, smaller and heavier title. |
Accessibility
- CardTitle isn't a heading. When the page outline needs one, put it inside:
<CardTitle><h2>Contacts</h2></CardTitle>. The title style doesn't change. - For a major section of the page, wrap the card in a
<section>whosearia-labelledbypoints at the title'sid. Don't do it for every card: too many regions make landmark navigation noisy. - CardDescription's muted text is 4.6:1 on white in Lightning Blue. On a gray fill like
bg-mutedit drops below 4.5:1, so keep it on the card surface.
Responsive behavior
- Card fills its container. Constrain it with the container or a class like
max-w-md. - Titles wrap. Add
truncateto CardTitle for a single-line title. - CardHeader is a named container (
@container/card-header), so header content can respond to the card's width. It also adds nothing to the card's intrinsic width: in a flex row or aw-fitlayout, give the card a width. - The card clips overflow. Wrap wide content, like a table, in a
divwithoverflow-x-auto, keep the default padding around focusable content so focus rings aren't cut off, and render menus and popovers in a portal (Popover and Dropdown Menu do by default).
Rules
- Keep CardAction inside CardHeader. Its grid placement only works there.
- Don't add radius, border, shadow or title size classes. The tokens set them per theme, and a class pins one look into every theme.
- Nest one level at most, with
size="sm"on the inner card only. - A Cosmos card has no visible border or shadow, so put it on the gray page (
bg-page). On a white surface, passborder-border. - The footer centers its content. Pass
justify-endfor form actions, orborder-t-0to drop the divider. - When View all navigates, render a link:
<a href="/contacts" className={buttonVariants({ variant: "link" })}>View all</a>. - For a table of records in a card, use the Related list block. Table draws its own border, so a Table under
px-0puts that border on the card's edges. For other full-bleed content, passpx-0to CardContent, and in a small card alsogroup-data-[size=sm]/card:px-0.