Table
Rows of records that share the same attributes.
Core · Show content
Groups a few facts about one thing on a bordered surface.
Two cards, each with a title and one fact about a service.
1,204 requests per minute.
48 active connections.
In a linked card, the body text, the price included, cannot be selected with a mouse: the title link covers the whole card, so a drag follows or moves the link. Find in page, keyboard selection and screen readers reach the text as usual.
<article class="luz-card">
<h3 class="luz-card__title">Database</h3>
<p class="luz-card__body">48 active connections.</p>
</article>
One card, six classes and two modifiers. A link in the title makes the whole card one link; any other link or button in it is raised above that link.
| Class or markup | What it does |
|---|---|
.luz-card | The surface: border, rounded corners and padding |
.luz-card | Base size, semibold, primary text colour |
.luz-card | Medium size, secondary text colour |
.luz-card | The price: base size, semibold, primary text colour, figures of equal width. A previous price goes in <s> |
.luz-card | The one card to look at first in a group: a 2 px border in the brand colour, as tall as its neighbours. Say why in words too, with a tag such as “Most chosen” |
.luz-card | An image on top, a body and actions at the foot: a flex column with no padding of its own, so the image runs to the border |
.luz-card | The <img>, full width, cropped to --luz-card-ratio (16 / 9 unless you set it), rounded on top only |
.luz-card | The links or buttons, pushed to the foot, so those of cards side by side line up |
<a> in .luz-card | A linked card: the link covers the card, the title turns sans, a darker border and a shadow on hover, a focus ring round the card |
Compose a card with the layout objects and the other components, with no new class: a linked card, an avatar or an icon beside the text with .o-media, an image on top, and a row of actions with .o-row.
Rows of records that share the same attributes.
Looks after billing and invoices.
A two-hour walk from the car park.
48 active connections.
<a> goes inside the heading, round its text. A second link or a button in it is allowed, and sits above the title link: see Product card below..o-media puts it beside .o-media__body, which holds the title and the body. Next to a name, hide the avatar with aria-hidden="true": it repeats the name..luz-image), inside the padding, with .u-mb-sm under it. Its alt text says what the picture shows; if it only repeats the title, leave it empty (alt="")..o-row with small buttons (.luz-btn--sm) or tags (.luz-tag), last in the card. In a linked card, one action at most, as a button after the title.A card for a shop is the card with an image, a linked title, a price and one action. The action is a button in a form, so it works without JavaScript; it is raised above the title link, so it takes its own click.
Bodega Albar · 75 cl
12,50 €
Bodega Albar · 75 cl
Now 9,90 € Was 12,50 €
<article>
<h4 id="red-wines">Red wines</h4>
<ul class="o-grid o-grid--auto" aria-labelledby="red-wines">
<li class="luz-card">
<div class="luz-image luz-image--4x3 u-mb-sm">
<picture>…<img src="malbec.jpg" alt="" width="640" height="480" loading="lazy" decoding="async"></picture>
</div>
<h5 class="luz-card__title"><a href="/wines/malbec-2021/">Malbec 2021</a></h5>
<p class="luz-card__body">Bodega Albar · 75 cl</p>
<p class="luz-card__price"><data value="12.50">12,50 €</data></p>
<form action="/cart/add" method="post">
<input type="hidden" name="product" value="malbec-2021">
<button class="luz-btn luz-btn--primary luz-btn--block" type="submit">Add to cart<span class="u-sr-only"> Malbec 2021</span></button>
</form>
</li>
</ul>
<output class="u-sr-only" id="cart-status"></output>
</article>
.luz-btn--block: a thumb finds it, and its label wraps and breaks inside the card at any text size..u-sr-only, repeating the link's text exactly (keep the space before it). A list of twelve buttons all called “Add to cart” says nothing to someone who lists the buttons of a page, or says them by voice.aria-labelledby points at the <h4> above it, and the cards use the next level. Core styles no list: remove the bullets and the indent with your own rule (list-style: none; margin: 0; padding: 0).<s>, with the words “Now” and “Was” hidden visually so the order is clear to a screen reader; the line through it is not colour.<output> (it has the role status) in the page from the start and write “Malbec 2021 added to the cart” into it; focus stays on the button. A status region that is created together with its text is often not announced. Without a script the form posts and the cart page is the feedback.<span class="luz-tag">Sold out</span>; do not disable the button.<img> with your own size rules.In a group of plans or offers, .luz-card--featured marks the one to look at first. The class says nothing to a screen reader, so the card carries a tag with the reason, in its text.
79 €
The talks and the workshops of one day.
Choose One dayNow 149 € Was 180 €
Both days, lunch and the recordings. Most chosen
Choose Two days590 €
Two days for five people, with a shared table.
Choose Team<article class="luz-card luz-card--featured">
<h4 class="luz-card__title">Two days</h4>
<p class="luz-card__price"><data value="149">149 €</data></p>
<p class="luz-card__body">Both days, lunch and the recordings.
<span class="luz-tag luz-tag--blue">Most chosen</span></p>
<a class="luz-btn luz-btn--primary luz-btn--block" href="/register/?ticket=two-days">Choose Two days</a>
</article>
For a project, a post or a place: an image, a few words and one or more actions. .luz-card--media makes the card a column, the image runs to its edge, and the actions sit at the foot, so in a grid the buttons line up whatever the length of the text.
Tides for any harbour on the coast, read from a public data set and kept up to date every night, with a chart of the week.
A small parser.
<article class="luz-card luz-card--media">
<img class="luz-card__media" src="tides.webp" alt="" width="1200" height="675">
<div class="luz-card__body o-stack">
<h3 class="luz-card__title">Tide table</h3>
<p>Tides for any harbour on the coast.</p>
</div>
<div class="luz-card__actions o-actions">
<a class="luz-btn luz-btn--primary" href="/tides/demo/">Open the demo<span class="u-sr-only"> of Tide table</span></a>
<a class="luz-btn luz-btn--secondary" href="/tides/code/">View the code<span class="u-sr-only"> of Tide table</span></a>
</div>
</article>
__actions needs none. Several destinations: no link in the title. Put each in __actions: a link round the title would cover the card and compete with the buttons..u-sr-only, so a list of links says which project each one opens.alt=""; otherwise say what the picture shows. Always give width and height, so the card does not jump when the image loads.--luz-card-ratio: 4 / 3 on the card, or on a parent, changes the shape for all its cards; the image is cropped, never stretched..o-actions).A container styled by components/card.css. No JavaScript.
<article> for a self-contained item, <div> for a group of facts, <li> inside a list of cards.--luz-radius-md, like a figure or a dropdown..luz-card--media is display: flex in a column with padding: 0: the image has none, the body and the actions take --luz-space-md. The actions have margin-block-start: auto, which pushes them to the foot of a card stretched to the height of its row. The image keeps aspect-ratio: var(--luz-card-ratio, 16 / 9) with object-fit: cover, and its top corners are the card's radius less the 1 px border; the card clips nothing, so the focus ring is whole.::after is stretched over the card (position: absolute; inset: 0), so a click anywhere in it follows the link. The card is position: relative to hold it. Any other link or control inside the card (a, button, input, select, textarea, summary) is position: relative with z-index: 1, so it paints above that overlay and takes its own click; the title link is the one thing never raised. A button's label wraps in a card (white-space: normal), so a long label or enlarged text stays inside it. On hover the border turns --luz-input-border and a small shadow shows; on keyboard focus a ring goes round the card. The title of a linked card is in sans, as a nav or a list of links is; a card without a link keeps the heading face. Both are found with :has(); a card without a link does not change on hover..luz-card--featured sets border-width: 2px in --luz-brand-primary and takes the extra pixel from the padding, so the card keeps the height of its neighbours. In a linked card the border turns --luz-brand-hover on hover, and the keyboard ring stands 4 px off the border, so it does not read as a thicker border.Nothing changes: a card, linked or not, a media card included, is HTML and CSS.
A card is not interactive and takes no focus.
role, tabindex or aria-label to the card.Contrast and focus are in the Design tab.
Use a card to group a small set of facts that read as one unit, often in a grid of similar items.
.luz-card--featured) for the one recommended choice in a group of plans or offers..luz-card--media) for a project, a post or a place with a picture, one destination or two actions.Do not use a card to box content that is not a separate thing.
<ul>): a card per link adds borders and nothing else.A short noun as the title, then one or two lines.
The title is primary text and the body secondary text, on the card surface. In a linked card the title is a link, and the border on hover and the focus ring sit between the card and the page.
A featured card has a brand border against both.
| Part | Colour on background | Light | Dark | High contrast | Needs |
|---|---|---|---|---|---|
Title | --luz-text-primary on --luz-surface | 16.5:1 | 14.0:1 | 21.0:1 | 4.5:1 |
Price | --luz-text-primary on --luz-surface | 16.5:1 | 14.0:1 | 21.0:1 | 4.5:1 |
Body | --luz-text-secondary on --luz-surface | 6.4:1 | 6.9:1 | 19.3:1 | 4.5:1 |
Title link | --luz-brand-primary on --luz-surface | 5.9:1 | 7.2:1 | 14.1:1 | 4.5:1 |
Border of a linked card, on hover | --luz-input-border on --luz-surface | 3.9:1 | 3.9:1 | 21.0:1 | 3:1 |
Border of a linked card, on hover, against the page | --luz-input-border on --luz-surface-page | 3.7:1 | 4.2:1 | 21.0:1 | 3:1 |
Border of a featured card | --luz-brand-primary on --luz-surface | 5.9:1 | 7.2:1 | 14.1:1 | 3:1 |
Border of a featured card, against the page | --luz-brand-primary on --luz-surface-page | 5.6:1 | 7.7:1 | 14.1:1 | 3:1 |
Border of a linked featured card, on hover | --luz-brand-hover on --luz-surface-page | 7.7:1 | 10.2:1 | 17.9:1 | 3:1 |
Focus ring round a linked card | --luz-focus-ring on --luz-surface-page | 5.6:1 | 7.7:1 | 21.0:1 | 3:1 |
Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings, borders and icons.
--luz-focus-ring, goes round the card, 2 px outside its border (4 px on a featured card, whose own border is 2 px thick). Both are outlines, so they stay in forced-colors mode.--luz-divider) is decorative and has no minimum: the content and the space around it group the card. On a linked card the hover border is --luz-input-border, 3:1 or more, with a shadow.The 19 tokens that components/card.css reads, by family. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.
--luz-brand-hover, --luz-brand-primary, --luz-divider, --luz-focus-ring, --luz-input-border, --luz-surface, --luz-text-primary, --luz-text-secondary--luz-space-2xs, --luz-space-md, --luz-space-sm--luz-radius-md--luz-font-sans, --luz-text-base, --luz-text-md--luz-shadow-sm--luz-duration-base, --luz-duration-fast, --luz-easing-enterKeyboard and ARIA are in the Code tab.