Analytical Engine
Project
Charles Babbage's design for a general-purpose mechanical computer, first described in 1837.
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.
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:
| Element | as | When |
|---|---|---|
<article> | The default | The 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.
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.
<h2> to <h6>, so it's a heading for assistive technologies too, rather than bold text.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.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.
Project
Charles Babbage's design for a general-purpose mechanical computer, first described in 1837.
<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>
<a> would make all of its text the link's name, read out every time someone tabs to it.<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, 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.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).
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).
| Key | Card |
|---|---|
Tab, Shift+Tab | Moves focus to the card's link, if it has one, and then on to its actions |
Enter | Follows 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.
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.
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.
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.
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.
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.
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.
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.
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.
@media (hover: hover)), otherwise a tap would leave the card looking hovered.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.
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.
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.
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.
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.
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>
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).| Rule | Level | Scope | Requirement | Basis | Check |
|---|---|---|---|---|---|
card/semantic-element | Must | Component | It's an <article> by default, or a <section>, <div> or <li> with as, and never a link or a button | Open Components, beyond WCAG 1.3.1 (A) | Unit test |
card/list-of-cards | Should | Usage | A set of cards is a list, with each card an <li> | WCAG 1.3.1 (A) | Review |
card/heading | Must | Both | Every card has a heading that says what it's about, at the level that fits the page's outline | WCAG 1.3.1 (A), 2.4.6 (AA) | Unit test, review |
card/heading-first | Should | Component | The heading comes first in the DOM, and the media and the eyebrow are shown above it with CSS alone | Open Components | Unit test |
card/stretched-link | Must | Component | A 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 card | Open Components, beyond WCAG 2.1.1, 4.1.2 (A) | Unit test |
card/link-name | Must | Component | The link is named by the heading alone | WCAG 2.4.4 (A) | Unit test |
card/link-purpose | Should | Usage | Headings say where the link goes, differ from card to card, and there's no separate "Read more" link | WCAG 2.4.4 (A), 2.4.9 (AAA) | Review |
card/actions-above | Must | Component | Actions and other links in the card sit outside the stretched link and above it, so pressing one runs it | Open Components | Unit test, review |
card/no-nesting | Must | Both | The link holds the heading's text alone, with nothing interactive inside it | HTML | Unit test |
card/keyboard | Must | Component | Tab reaches the card's link once and then its actions, and Enter follows the link | WCAG 2.1.1, 2.4.3 (A) | Unit test |
card/media-alt | Must | Usage | An image that only illustrates the heading has alt="", and one that shows more has alt text that says what | WCAG 1.1.1 (A) | axe image-alt, review |
card/static-media | Must | Usage | The media holds nothing people can focus, since it's shown above the heading but comes after it in the DOM | WCAG 1.3.2, 2.4.3 (A) | Keyboard |
card/dom-order | Must | Usage | Cards come in the same order in the DOM as on screen, without order, row-reverse or a dense grid | Open Components, beyond WCAG 1.3.2, 2.4.3 (A) | Keyboard |
card/focus-not-obscured | Must | Usage | Sticky headers and footers never cover a focused card, for example thanks to scroll-padding | WCAG 2.4.11 (AA), 2.4.12 (AAA) | Keyboard |
card/no-truncation | Should | Usage | Headings aren't cut off with an ellipsis or a line clamp | Open Components | Review |
card/announce-results | Should | Usage | Cards that load after something people did, like a search, are announced with a status message | WCAG 4.1.3 (AA) | Screen reader |
card/mirrors-in-rtl | Should | Component | It uses logical properties, so it mirrors itself in right-to-left layouts | Open Components | Review |
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 products | Cards, in a list |
| Compare many things by their details, like prices and dates | Table |
| Pick one option, like a plan, as part of a form | Radio Group, which can look like a set of cards |
| Be told something important, like an error | Alert |
| Read through a stream that loads more as they scroll | A list of cards, built on the APG's Feed pattern |
The Table, the Radio Group and the Alert are on our Roadmap.
Ctrl or Cmd, or the middle mouse button, which is one of the things a real link gives them.