Skip to main content

Design system components

Card

Quiet bordered card surface for discrete, self-contained content, including cards with actions composed into footer slots.

Status: stable. Group: structure. Markdown version: /design-system/components/card.md

When to use

Card is the quiet surface for discrete, self-contained content; things that could be reordered, removed, or interacted with independently. The container owns background, hairline border, radius, and padding; variant only swaps the background (default with hairline, muted, transparent) and padding picks a step on the space scale. A thin content layer covers the common patterns: title (a real heading at xxs), description, headerStart and headerEnd header regions (headerEnd replaces the deprecated extra) and footerStart/footerEnd action regions. Everything richer; dividers, footers, media, tabs; is composition with Divider, FlexBox, Stack, and friends. link turns the whole card into exactly one anchor with the focus ring on the frame.

When not to use

  • Do not use the muted variant to encode status; status belongs to AlertBanner or Badge.

Look-alikes

  • AlertBanner: Do not use the muted variant to encode status; status belongs to AlertBanner or Badge.
  • Badge: Do not use the muted variant to encode status; status belongs to AlertBanner or Badge.
  • LocationCard: Do not use as a generic 'card with an icon and label'; that is Card.
  • Section: Do not wrap page sections or whole forms in cards, and never nest cards in cards; that is Section/Stack territory.
  • SelectableCard: Do not nest buttons or links inside a selectable card; use Card for action-oriented content.
  • Stack: Do not wrap page sections or whole forms in cards, and never nest cards in cards; that is Section/Stack territory.
  • ValueCard: Do not use for content with a primary CTA; that is ServiceCard or Card.

Props

Card props
PropTypeDefaultDescriptionRequired
variant"default" | "muted" | "transparent"defaultBackground variant. All variants keep a transparent border so switching never causes layout jitter. - default: surface background with a hairline border - muted: recessed light background, no visible border - transparent: no background, no border; grouping without weight.No
padding"sm" | "md" | "lg" | "none"mdInternal padding step on the space scale (12/16/24px)No
as"article" | "div" | "section" | "aside" | "li"divSemantic element for the card surface.No
titlestringnoneOptional heading rendered at the top of the card.No
titlePropsCardTitleProps{}Heading configuration.No
descriptionstringnoneOptional supporting line under the title.No
descriptionPropsCardDescriptionProps{}Description configuration.No
headerStartReact.ReactNodenoneLeading header region. Defaults to the title heading block when omitted.No
headerEndReact.ReactNodenoneTrailing header region (badge, metadata, menu). Canonical replacement for extra.No
extraReact.ReactNodenoneNo description in the contract. Deprecated: Deprecated.No
footerStartReact.ReactNodenoneLeading footer region; the special/destructive action, pinned left.No
footerEndReact.ReactNodenoneTrailing footer region; 1–2 action buttons, pinned right.No
linkstringnoneMakes the whole card one link to this href.No
linkLabelstringnoneAccessible name for the link when title alone is ambiguous.No
loadingbooleanfalseSkeleton loading state: the card is aria-busy and its skeleton is the one status region ("Loading").No
childrenReact.ReactNodenoneNo description in the contract.No

Variants

  • Available variants: default, muted, transparent (default default).
  • Available paddings: none, sm, md, lg (default md).
  • Allowed as values: article, div, section, aside, li (default div).

Invalid combinations

  • MUST NOT: When link is set, then do not set footerStart or footerEnd. link is ignored when footerStart/footerEnd are set: a stretched anchor cannot wrap interactive footer controls. Use a plain card with buttons instead.

Keyboard

Card keyboard behaviour
KeyBehaviour
TabMoves focus to the control and on to the next focusable element. (standard)
EnterActivates the focused control. (standard)

Rows marked (standard) name a key the contract lists without a description; the behaviour shown is the usual one for that key.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

Card rules
LevelRuleHow it is checked
MUST NOTWhen link is set, then do not set footerStart or footerEnd. link is ignored when footerStart/footerEnd are set: a stretched anchor cannot wrap interactive footer controls. Use a plain card with buttons instead.Checked by the MCP tool validate_component_usage (rule card-link-with-footer). Also enforced in code: Card.tsx development warning (linkSuppressed).
MUSTARIA: Title renders a real heading (level via titleProps.level, default 3)Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Link mode renders exactly one stretched anchor named by linkLabel ?? title; focus ring draws around the card frame.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Loading sets aria-busy on the card and renders skeleton lines; the skeleton is the one status region and announces the translated "Loading".Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Plain cards are not focusable and add no roles.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.
SHOULDCompose structure: Title/Text children, a Divider where the split carries meaning, a FlexBox row as the footer; the card is just the surface.Not stated in the contract.
SHOULDKeep padding consistent across sibling cards in a grid; pick one step per surface.Not stated in the contract.
SHOULDSet titleProps.level to keep the document outline honest; grids under a section heading typically use level 3.Not stated in the contract.
SHOULDPass linkLabel on linked cards whose title is ambiguous out of context; "Read more" is the failure mode.Not stated in the contract.
SHOULDUse muted for de-emphasised siblings and transparent to group without visual weight; reach for spacing and headings before adding cards at all.Not stated in the contract.
SHOULD NOTDo not nest interactive controls inside a link-mode card; use a plain card with a single Button instead.Not stated in the contract.
SHOULD NOTDo not wrap page sections or whole forms in cards, and never nest cards in cards; that is Section/Stack territory.Not stated in the contract.
SHOULDPut the destructive/special action in footerStart and the confirm/cancel buttons in footerEnd; footerEnd hugs the right even when footerStart is empty.Not stated in the contract.
SHOULDWhen you replace the title with custom headerStart content, keep a real heading inside it so the document outline stays honest.Not stated in the contract.
SHOULD NOTDo not combine link mode with the action footer: a stretched anchor cannot wrap buttons, so Card drops the link and warns in dev when footerStart/footerEnd are set. Likewise keep interactive content (a menu) out of a linked card's header; Card cannot detect a ReactNode's interactivity there, so that case is the consumer's responsibility.Not stated in the contract.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { Card } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/layout exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-card>; see the custom elements manifest.