Card

User Experience (UX)

How a card behaves for every person, input and setting, so it's accessible, predictable, clear in every state and adaptive.

Now let's look at how a card behaves for every person, input and setting. A good card is accessible, predictable, designed in every state and adaptive.

Accessible

Use the right element

The Card renders an <article> by default, which HTML defines as a self-contained piece of a page, one you could take out and still make sense of, like a project, a product or a post. That's what most cards are. The as prop picks another element when a card isn't one of those:

ElementasWhen
<article>The defaultThe card stands on its own, like a project or a product
<li>"li"The card is one of a list, inside a <ul> or an <ol>
<section>"section"The card is a part of the page, like a settings panel, rather than a thing of its own
<div>"div"The card is only a box, and its content says everything

We recommend never making the card itself a link or a button. A card holds a heading, text and sometimes actions of its own, and a link or a button can't hold any of them well: its whole content becomes its name, and it can't have another link or button inside it. When the whole card should take people somewhere, its heading holds the link, as under Make the heading the link.

Give every card a heading

The heading is how people find a card. Most screen reader users look for what they need on a page by moving from heading to heading, according to every WebAIM Screen Reader User Survey, and the heading is what they hear first when they land on a card. It's also what sighted people read first, and what tests and agents look for.

  • Every card has a heading that says what it's about, like the project's name (WCAG 1.3.1, 2.4.6). The Card renders it as an <h2> to <h6>, so it's a heading for assistive technologies too, rather than bold text.
  • Pick the level that fits the page's outline, with headingLevel. Cards in a section titled with an <h2> get an <h3>, which is the default, while cards right under the page's <h1> get an <h2>. A level that skips one, like an <h4> under an <h2>, makes people wonder what they missed.
  • Give each card in a list a different heading, so people can tell them apart when they list the page's headings or links.

When the whole card takes people somewhere, like a project's page, put the link in the heading, around the heading's text, and stretch it over the card. href does that for you. The link is still just the heading's text, so it's named by the heading alone, while a pseudo-element of the link covers the whole card, so pressing anywhere on the card follows it.

Charles Babbage's design for a general-purpose mechanical computer, first described in 1837.

By Charles Babbage
<Card
  heading="Analytical Engine"
  href="/projects/analytical-engine"
  eyebrow="Project"
  media={<img src="/projects/analytical-engine.jpg" alt="" />}
  actions={
    <>
      <Button size="sm" variant="outline" aria-pressed={saved} onClick={() => setSaved(!saved)}>Save</Button>
      <Button size="sm" variant="ghost" onClick={share}>Share…</Button>
    </>
  }
  footer="By Charles Babbage"
>
  <p>Charles Babbage's design for a general-purpose mechanical computer, first described in 1837.</p>
</Card>
  • The link's name is the heading, like "Analytical Engine", so screen readers announce a short name that says where it goes, and voice control users can say "click Analytical Engine" (WCAG 2.4.4, 2.5.3). Wrapping the whole card in an <a> would make all of its text the link's name, read out every time someone tabs to it.
  • It's a real <a href>, so it works like every other link. People can open it in a new tab with Ctrl or Cmd, or with the middle mouse button, copy its address, and see where it goes before they press it. A card with a click handler or role="link" loses all of that, and keyboard users can't reach it at all unless it has a tabindex.
  • Actions, like Save, stay outside the link, in actions, and sit above the stretched link, so pressing one runs it rather than following the link. Tab reaches them right after the link. HTML doesn't allow a button inside a link, which is one more reason the card itself is never the link.
  • A link inside the body or the eyebrow, like a link to the project's author, sits above the stretched link too, so it goes where it says. Keep them few, though, since a card with many links in it is harder to press in the right place.

Describe the media

An image that only illustrates the heading, like a photo of the product it names, gets an empty alt="", which tells screen readers to skip it. If its alt text repeated the heading, people would hear the same thing twice. An image that shows something the text doesn't, like a chart or a floor plan, needs alt text that says what it shows (WCAG 1.1.1).

Contrast and size

  • The text needs a contrast of at least 4.5:1 with the card's background, and so do the eyebrow and the footer, since people need to read them too (WCAG 1.4.3). Text over an image is hard to keep at 4.5:1, which is why the Card puts its text below the media rather than on top of it.
  • The border doesn't need 3:1, since it doesn't tell people anything the heading doesn't (WCAG 1.4.11). The focus ring does, against the page.
  • A card that's a link is a large target already, since the whole card follows the link. Its actions are Buttons, which are at least 24 × 24px (WCAG 2.5.8), with a hit area of 44 × 44px on touch screens. Leave a gap between them, as the actions do, so a finger doesn't press the one next to it.

Show focus

When a card's link has focus, the whole card shows an outline in the same focus colour as every other component (WCAG 2.4.7). It's drawn by the link's pseudo-element, which covers the card, so it goes around the whole card rather than the heading's text, which is where people can press. Like the Button's, it's at least 2px thick and drawn outside the border, and we recommend 2px for both. An action shows its own focus ring instead, so only one thing on the page ever looks focused.

You'll also want to keep focused cards clear of sticky headers and footers, which you can do with scroll-padding (WCAG 2.4.11, 2.4.12).

Predictable

Keyboard

KeyCard
Tab, Shift+TabMoves focus to the card's link, if it has one, and then on to its actions
EnterFollows the link

A card has no keys of its own. It's a link and some Buttons, which Tab reaches in the order they come in, like on the rest of the page. We recommend not adding arrow keys to move between cards, since people don't expect them on a list of links, and they'd have to find out that they work. A list where the arrow keys do move between items, like a feed that loads more as people scroll, is a pattern of its own, which the APG calls a Feed.

Put the heading first

The heading comes first in the card's DOM, before the eyebrow and the media, while CSS shows the media and the eyebrow above it. People who move from heading to heading land at the start of the card, and hear what it's about before anything else. With an eyebrow or an image before the heading in the DOM, they'd land past them, and miss them.

Moving content around with CSS is only safe for content people can't focus. Tab follows the DOM, so if the media held a link or a button, focus would go to the heading's link first and then jump back up to the media. Keep the media an image, and put anything people can press in the body, the actions or the footer.

Write headings that say where they go

When a card is a link, its heading is the link's name, so it's best to write it like one: the name of the thing the card is about, rather than "Read more" or "Learn more". People who list the links on a page see the names alone, and five links called "Read more" don't say where any of them go (WCAG 2.4.4). For the same reason, we recommend not adding a separate "Read more" link next to the stretched one, which would only be a second way to the same place.

Don't nest interactive elements

The link holds the heading's text and nothing else. HTML doesn't allow a link or a button inside a link, so nothing defines what browsers and screen readers should do with one. Actions go in actions, which is outside the link, and the stretched link only covers them on screen, where the Card lifts them above it.

Every state

Linked

A card that's a link shows that it is when people point at it or tab to it. On hover, its border gets stronger and its heading is underlined, which tells people the whole card does something before they press it. On focus, it shows its focus ring. The pointer over the card is the link's own, so people see the hand they'd see over any other link.

With actions

A card can have actions of its own, like Save or Share, whether it's a link or not. They're Buttons, so they keep their names, their states and their focus rings, and pressing one runs it, even on a card that's a link. Once an action has done something, like saving the project, its state shows on the Button, like aria-pressed on a Save toggle, rather than on the card.

Without media

Not every card has an image, and an image can fail to load. The media keeps its space whatever its image does, with aspect-ratio, so cards in a row stay lined up, and the page doesn't jump when an image arrives late. While it loads, or if it fails, the space shows a faint tint of the text colour instead of a gap. If some cards in a list have no image at all, leave the media out of them, rather than showing an empty box.

Loading

While cards load, hold their place with placeholders the same size as the cards, so the page doesn't jump around when they arrive. A Skeleton, on our Roadmap, will do that for you. If the cards load because of something people did, like a search, say how many arrived in a status message, since a screen reader user won't see them appear.

Long content

Headings and text wrap rather than being cut off, and the card grows to fit them. We recommend not cutting headings off with an ellipsis or a line clamp. People can't read the rest, a screen reader still reads all of it, so the two disagree, and a translated heading is often longer than the original.

Adaptive

Pointer, touch and pen

  • Hover styles only apply to pointers that can actually hover (@media (hover: hover)), otherwise a tap would leave the card looking hovered.
  • On touch screens, the whole card follows its link, so it's an easy target, while its actions are Buttons with hit areas of their own, at least 44 × 44px.
  • The stretched link covers the card's text, so pressing and dragging on it drags the link rather than selecting the text. If people need to copy something from a card, like a code or an address, put it on the page the card links to, or leave that card without a link.

Motion

The Card doesn't move. If you'd like a card to lift or grow on hover, wrap the animation in @media (prefers-reduced-motion: no-preference), so it only runs for people who haven't asked for less motion (WCAG 2.3.3). Keep in mind that a card that grows can cover the ones next to it.

More contrast

When someone asks for more contrast (prefers-contrast: more), as with Increase contrast on macOS and iOS, the border is drawn in the text colour at full strength, rather than at 20%. The text, the eyebrow and the footer already pass 4.5:1, so they stay as they are.

Forced colours

Windows contrast themes replace every colour with the user's own, and drop backgrounds and shadows. The Card's border stays, since the system draws it in a colour of its own, focus is drawn with outline, in Highlight, and the media's <img> keeps its image. That's why the Card draws its boundary with a border rather than a shadow, which these themes would drop.

Colour scheme

The tokens use light-dark(), which follows color-scheme, so the Card adapts to your light and dark themes on its own. If you give it a background, use a token that does the same, and check the text against it in both.

Text size and spacing

Nothing in the Card has a fixed height, so it grows with its text instead of clipping it, at 200% text size (WCAG 1.4.4) and with wider line, letter and word spacing (WCAG 1.4.12). Long words, like a web address, wrap rather than overflow the card. Only the media keeps its shape, since it's sized by its width and its aspect ratio.

Languages and direction

The Card uses logical properties, so it mirrors itself in right-to-left languages, with its actions starting from the right:

تصميم تشارلز باباج لحاسوب ميكانيكي عام الغرض، وُصف لأول مرة عام 1837.

<Card
  dir="rtl"
  lang="ar"
  heading="المحرك التحليلي"
  href="/ar/projects/analytical-engine"
  eyebrow="مشروع"
  media={<img src="/projects/analytical-engine.jpg" alt="" />}
  actions={<Button size="sm" variant="outline">حفظ</Button>}
>
  <p>تصميم تشارلز باباج لحاسوب ميكانيكي عام الغرض، وُصف لأول مرة عام 1837.</p>
</Card>
  • In right-to-left layouts, it's usually the page that sets dir and lang, on <html>, and the Card follows it. Set them on a card when its language differs from the page's, like one card in a list of translations (WCAG 3.1.2).
  • Images don't mirror themselves, which is usually right, since a photo flipped left to right looks wrong. Only flip an image that points along the reading direction, like an arrow.

UX Checklist

RuleLevelScopeRequirementBasisCheck
card/semantic-elementMustComponentIt's an <article> by default, or a <section>, <div> or <li> with as, and never a link or a buttonOpen Components, beyond WCAG 1.3.1 (A)Unit test
card/list-of-cardsShouldUsageA set of cards is a list, with each card an <li>WCAG 1.3.1 (A)Review
card/headingMustBothEvery card has a heading that says what it's about, at the level that fits the page's outlineWCAG 1.3.1 (A), 2.4.6 (AA)Unit test, review
card/heading-firstShouldComponentThe heading comes first in the DOM, and the media and the eyebrow are shown above it with CSS aloneOpen ComponentsUnit test
card/stretched-linkMustComponentA card that's a link has one link, in its heading, stretched over the card with a pseudo-element, and never a wrapping <a>, role="link" or a click handler on the cardOpen Components, beyond WCAG 2.1.1, 4.1.2 (A)Unit test
card/link-nameMustComponentThe link is named by the heading aloneWCAG 2.4.4 (A)Unit test
card/link-purposeShouldUsageHeadings say where the link goes, differ from card to card, and there's no separate "Read more" linkWCAG 2.4.4 (A), 2.4.9 (AAA)Review
card/actions-aboveMustComponentActions and other links in the card sit outside the stretched link and above it, so pressing one runs itOpen ComponentsUnit test, review
card/no-nestingMustBothThe link holds the heading's text alone, with nothing interactive inside itHTMLUnit test
card/keyboardMustComponentTab reaches the card's link once and then its actions, and Enter follows the linkWCAG 2.1.1, 2.4.3 (A)Unit test
card/media-altMustUsageAn image that only illustrates the heading has alt="", and one that shows more has alt text that says whatWCAG 1.1.1 (A)axe image-alt, review
card/static-mediaMustUsageThe media holds nothing people can focus, since it's shown above the heading but comes after it in the DOMWCAG 1.3.2, 2.4.3 (A)Keyboard
card/dom-orderMustUsageCards come in the same order in the DOM as on screen, without order, row-reverse or a dense gridOpen Components, beyond WCAG 1.3.2, 2.4.3 (A)Keyboard
card/focus-not-obscuredMustUsageSticky headers and footers never cover a focused card, for example thanks to scroll-paddingWCAG 2.4.11 (AA), 2.4.12 (AAA)Keyboard
card/no-truncationShouldUsageHeadings aren't cut off with an ellipsis or a line clampOpen ComponentsReview
card/announce-resultsShouldUsageCards that load after something people did, like a search, are announced with a status messageWCAG 4.1.3 (AA)Screen reader
card/mirrors-in-rtlShouldComponentIt uses logical properties, so it mirrors itself in right-to-left layoutsOpen ComponentsReview

FAQ

Card or something else?

A card summarises one thing, so people can scan a few of them and pick one. For anything else, another component will do a better job:

When people need to…Use
Scan a few things and pick one, like projects or productsCards, in a list
Compare many things by their details, like prices and datesTable
Pick one option, like a plan, as part of a formRadio Group, which can look like a set of cards
Be told something important, like an errorAlert
Read through a stream that loads more as they scrollA list of cards, built on the APG's Feed pattern

The Table, the Radio Group and the Alert are on our Roadmap.

Copyright © 2026