Card

Card is a styled container component with border, box-shadow, and border-radius that groups related content. It supports structured layouts with subcomponents or custom content.

Page navigation navigation

By default, Card renders a <div>, which has no implicit semantics. The right pattern depends on how the Card is used: as a standalone region, or as one item in a collection.

Choose the right semantics for the context

Standalone Card

When a Card stands on its own as a meaningful chunk of page content, render it as a <section> so it is exposed as a landmark.

<Card as="section">
  <Card.Heading>Project Alpha</Card.Heading>
  <Card.Description>Card content</Card.Description>
</Card>

When Card.Heading is present, the section's aria-labelledby is automatically wired to the heading's id. If there is no visible heading, provide your own aria-label or aria-labelledby on the Card.

A visible heading is strongly recommended on standalone Cards so that sighted and non-sighted users have an equivalent experience. Choose a Card.Heading level (h2 through h6) that fits the surrounding document outline. The default is h3.

Card in a collection

When multiple Cards are rendered together, place them inside a list. The <li> provides grouping, so each Card should remain a <div> (the default) and should not also be set to as="section".

<ul>
  <li>
    <Card>
      <Card.Description>Card within a list</Card.Description>
      <Card.Metadata>Updated 2 hours ago</Card.Metadata>
    </Card>
  </li>
  {/* …more list items */}
</ul>

Headings must not be used inside a list-item Card. Heading elements combined with list semantics disrupt the reading order for screen reader users. Use Card.Description (or other non-heading content) for the primary text, and avoid heading elements in custom content too.

Provide context for interactive controls

Card.Action renders a single interactive control (typically an IconButton) in the top-right corner. The control's accessible name must identify which Card it acts on. "More options" is not enough when the page contains several Cards.

<Card.Action>
  <IconButton icon={KebabHorizontalIcon} aria-label="More options for Project Alpha" variant="invisible" />
</Card.Action>

The same applies to any interactive content passed as Card children. When several Cards expose the same controls, include the Card's subject in each control's accessible name. Visually hidden text is a common way to do this.

Images and icons

Card.Image accepts the standard <img> attributes. Provide a meaningful alt value for informative images, and an empty string (the default) for decorative ones.

Card.Icon is decorative by default and is hidden from assistive technologies. Only set aria-label when the icon conveys information that is not already in the surrounding text.

How to test the component

Integration tests

  • A standalone Card is exposed as a region landmark with an accessible name that reflects the Card's subject
  • A list of Cards is rendered as a labelled <ul> or <ol>, with each Card inside an <li>, and the Cards do not introduce additional landmarks
  • No heading elements are present inside list-item Cards
  • When multiple Cards expose the same controls, each control's accessible name identifies which Card it acts on
  • Informative Card.Image images have meaningful alt text; decorative images have an empty alt

Component tests

  • Card renders as <div> by default, and as <section> when as="section"
  • When as="section" and a Card.Heading is present, the section's aria-labelledby is automatically wired to the heading's id
  • Card.Heading renders at the configured heading level, defaulting to <h3>
  • Card.Icon is hidden from the accessibility tree unless an aria-label is provided
  • Card.Action exposes its child control with the consumer-provided accessible name