Card

ui.css · Cross-system: Cross-system reference § Content

Overview

<article> is the card primitive — self-contained content with title, meta, and optional actions. Styling is applied directly to the element; no class is needed.

Anatomy

Usage

Show code

With leading media

.card-media puts a picture of the card's subject in a track before the body. Draw the tile even when there is no picture — an <ui-icon> as a placeholder keeps titles aligned so the list is easy to scan. Omitting the element instead returns that card to the two-column grid, misaligning its title with the others.

Show code

Treat the picture as decorative unless it conveys information absent from the card text. Where it restates a title already beside it, give it alt="" — otherwise a screen reader hears every row twice. That is separate from .card-link's accessible-name rule below: a .card-media outside the link is not part of the link's name, but it is still part of the card's.

Keep .card-media beside the <a> so the image does not become part of the link's accessible name. The link's stretched click target already covers the image, so clicking the image and clicking the title perform the same action. Do not move the image inside the anchor just to make it clickable.

States

The card reacts only where it is actually clickable. Both hover effects — the darker border and the brand-coloured h4 — are keyed off article:has(> .card-link:hover, > .card-link:focus-visible), so a card whose text is not wrapped in a .card-link has no hover effect. On clickable cards, the stretched link makes this effect respond across the card.

Only an active link or button receives the stretched target. The overlay is written article > .card-link:is([href], [role="button"])::after. A card whose link is inert — aria-disabled="true" with a title saying why, and neither href nor role — keeps the small target and therefore stays inert across its surface. Without this condition, the card would highlight on hover even though it cannot be opened. The text remains inside the anchor, whose title explains why the card is unavailable.

The focus ring surrounds the whole card. It is drawn by article:has(> .card-link:is([href], [role="button"]):focus-visible) and the anchor's own ring is dropped inside a card only, so a .card-link used outside one — a side-panel row — keeps it. An indicator around the text while the whole card responds does not show the full clickable area (WCAG 2.4.11 Focus Not Obscured / 2.4.13 Focus Appearance).

A non-interactive card needs no cursor override. The bare article rule in ui.css sets no cursor at all, so a card with nothing clickable in it resolves to auto on its own; the pointer comes from whatever is clickable inside. Cards do not have cursor: pointer by default, so read-only lists need no cursor override.

Override both layout properties when using <article> as a plain container. The rule sets display: grid and align-items: start. If only the first is overridden, rows can remain sized to their content instead of stretching across the card — a header using justify-content: space-between then has no extra width to distribute, and a footer’s border-top spans only its content. A wrapping metadata row can also retain its max-content width and cause horizontal overflow in the card’s container. Set align-items: stretch alongside the display override, or keep the grid and let & > :not(.card-link, menu) span both columns as the Anatomy section above describes.

Accessibility

Cross-system

See Cross-system reference § Content for Bootstrap .card and Open UI card.research.

See also