Card
Card is a styled container component that groups related content. By default it renders a div element, which carries no implicit semantics. The right markup pattern depends on how the Card is used: as a standalone region, or as one item in a collection (list).
Page navigation navigation
Accessibility and usability expectations
Presentation
Cognition
- A
Cardmust have a clear primary label so that all users can understand its purpose. This can be provided using either a visibleCard.Headingor with sufficient descriptive text - When multiple
Cardcomponents are displayed together, each Card's content must be distinctive so that users can identify it easily.
Vision
- Do not rely on color alone to convey information in the
Card. Instead, use text labels or other indicators for people who cannot perceive color differences - All text within a
Cardmust have a color contrast ratio of at least 4.5:1 against its background - Meaningful non-text elements, such as informative icons and the visual boundaries of UI components like buttons and input fields, must have a contrast ratio of at least 3:1 against adjacent colors
- Interactive controls inside the
Cardmust have a clearly visible focus indicator with a minimum contrast ratio of 3:1 against adjacent colors - When the
Cardcontains multiple controls, visible hover and focus styles should be applied to controls within theCardand not to theCardcomponent itself, so that people can understand which specific control they are interacting with
Mobility
- All interactive controls in a
Cardmust meet the minimum target size of 24 by 24 CSS pixels for easier activation by people with limited dexterity - There must be sufficient spacing between interactive controls so that each can be activated without accidentally triggering adjacent controls. For any control smaller than 24 by 24 CSS pixels, the space around it must be large enough that a 24 CSS pixel diameter circle centered on the control does not overlap any adjacent control.
Adaptability
- The
Cardmust be responsive, ensuring all content remains readable without horizontal scrolling when zoomed in up to 200% or viewed at a viewport size of 320 by 256 CSS pixels - When adding additional line or letter spacing, make sure that the
Card's content can still be read in its entirety, and that it is not clipped or hidden
Interaction
Keyboard
- All interactive controls inside a
Cardmust be reachable and operable using a keyboard alone - Focus should move through the
Card's controls in a logical order that matches the visual reading order - Focus must not become trapped inside a
Card
Engineering for AT Compatibility
Screen reader
- A standalone
Cardrendered as a<section>must have an accessible name so that screen reader users can identify it when navigating by landmarks. This is wired automatically when aCard.Headingis present; alternatively, passaria-labeldirectly on theCardelement to supply the accessible name without a visible heading. - Related content inside a
Cardmust follow a logical reading order that reflects the visual layout, so that the sequence announced by a screen reader matches what sighted users perceive Cards rendered inside a list must not introduce additional landmark regions; eachCardrendered inside a list should remain a<div>- Interactive controls must each have a descriptive accessible name that identifies the specific
Cardthey act on; a generic label like "More options" is insufficient when multiple Cards expose the same control. Instead, provide context using the card's topic with descriptive names like "More options for Project Alpha" Card.IconandCard.Imagecan each be treated as decorative or informative (they are treated as decorative by default). If the icon or image conveys information that is not already expressed in the surrounding text, provide an accessible alternative (aria-labelforCard.Icon; a meaningfulaltvalue forCard.Image). If the surrounding text already communicates the same information, treat the element as decorative to avoid redundant announcements
Speech recognition
- Visible text labels on interactive controls must be included in the control's accessible name so that speech recognition users can activate controls by speaking the visible text
- When visually hidden text is used to supplement a control's accessible name (for example, appending the
Card's heading to a button), the visible label text should appear at the start of the accessible name
Built-in accessibility features
- By default,
Cardrenders as a<div>with no implicit landmark role - When
as="section"is set, theCardrenders as a<section>element. It is exposed as a landmark region when it has an accessible name (for example, fromCard.Headingviaaria-labelledby, or from anaria-labelpassed directly toCard) - When
Card.Headingis present alongsideas="section", the section'saria-labelledbyattribute is automatically wired to the heading'sid, providing the landmark with an accessible name without any extra markup Card.Headingrenders as<h3>by default; the heading level can be changed via theasprop to maintain the correct document outline- The component is responsive and adjusts to available space. It should not introduce horizontal scrolling by itself; content authors are responsible for ensuring custom content reflows appropriately
Implementation requirements
Choosing the right semantics
The appropriate markup depends on how the Card is used.
Standalone Card
When a Card stands on its own as a meaningful chunk of page content, render it as a <section>. Ensure it has an accessible name so it is exposed as a landmark region that screen reader users can navigate to directly.
<Card as="section">
<Card.Heading>Project Alpha</Card.Heading>
<Card.Description>Card content</Card.Description>
</Card>
When Card.Heading is present, aria-labelledby is automatically wired to the heading's id. If there is no visible heading, provide your own accessible name directly on the Card using aria-label:
<Card as="section" aria-label="Project Alpha">
<Card.Description>Card content</Card.Description>
</Card>
Choose a Card.Heading level (h2 through h6) that fits the surrounding document outline without skipping heading levels. The default is h3.
Card in a collection
When multiple Cards are rendered together, place each Card inside a list item (<li>). The list provides the grouping semantics, so each Card should remain a <div> (the default) and must not be set to as="section".
<ul aria-label="Repositories">
<li>
<Card>
<Card.Description>primer/react</Card.Description>
<Card.Metadata>Updated 2 hours ago</Card.Metadata>
</Card>
</li>
{/* …more list items */}
</ul>
Do not use heading elements (Card.Heading or custom <h2>–<h6>) inside list-item Cards. In a collection, the list items already group each Card's content, so adding headings creates redundant and unnecessary structure for screen reader users. Use Card.Description or other non-heading content for the Card's primary text.
Providing context for interactive controls
Card.Action renders a single interactive control (typically an IconButton) in the top-right corner of the Card. The control's accessible name must clearly identify which Card it acts on. A generic label like "More options" is insufficient when the page contains several Cards.
<Card.Action>
<IconButton
icon={KebabHorizontalIcon}
aria-label="More options for Project Alpha"
variant="invisible"
/>
</Card.Action>
The same principle applies to any interactive content passed as Card children. When several Cards expose identical controls, include the Card's subject in each control's accessible name. Visually hidden text is a common technique to do this while keeping the visible label concise:
<Button leadingVisual={StarIcon} size="small">
Star <VisuallyHidden>primer/react</VisuallyHidden>
</Button>
Images and icons
Card.Image accepts the standard <img> attributes. When the image is decorative, no action is needed because alt="" is applied automatically by default. Provide a meaningful alt value only when the image conveys information that is not already available in the surrounding text.
{/* Informative images need a meaningful text alternative using the alt attribute */}
<Card.Image src="octocat.png" alt="Octocat" />
Card.Icon is decorative by default and is hidden from assistive technologies. Only provide an aria-label when the icon conveys information that is not already expressed in the surrounding text.
How to test the component
Integration tests
- A standalone
Cardis exposed as a region landmark with an accessible name that reflects theCard's subject - Where the same control appears on multiple
Cards, for example, a "Star" button on each repository Card, each instance has a unique accessible name that includes theCard's subject, so users can tell them apart - Informative
Card.Imageimages have a meaningful text alternative using thealtattribute and reflect the image content in context - The document heading structure is maintained.
Cardheadings fit the surrounding page outline without skipping levels
Component tests
Cardrenders as<div>by default and as<section>whenas="section"is set- When
as="section"and aCard.Headingare both present, the section'saria-labelledbyreferences the heading's uniqueid Cards rendered inside a list item do not introduce additional landmark regions- No heading elements are present inside list-item
Cards Card.Iconhasaria-hidden="true"by default and is hidden from the accessibility tree; when anaria-labelis provided, the icon is exposed with that labelCard.Imagerenders with the providedaltprop; when noaltis supplied, it defaults to an empty string- Interactive controls within example
Cardcontent have an accessible name - Interactive controls within example
Cardcontent meet the minimum target size of 24 by 24 CSS pixels - Text, interactive elements, and meaningful graphics in example
Cardcontent meet minimum color contrast requirements (4.5:1 for text; 3:1 for non-text elements) - Example
Cardcontent remains fully readable without horizontal scrolling when zoomed or viewed at a width equivalent to 320 CSS pixels