Entry 05

Whole card clickable from one link

Use it for cards, list rows and tiles where the whole area should navigate. It replaces an <a> wrapped around the card, which makes a screen reader read every word as the link text, and it replaces a click handler on a div.

Open
Click anywhere on the card, the corners and the paragraph included: the link count goes up. Click Save: only the button count goes up. Tab to the link: the ring is drawn round the whole card.
Code HTML · CSS · 2 blocks · 24 lines
<article class="card">
  <h3><a class="card-link" href="/reports/q3">Quarterly report</a></h3>
  <p>Revenue grew in every region.</p>
  <button type="button">Save</button>
</article>
.card { position: relative; }

.card-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.card-link:focus-visible { outline: 2px solid transparent; }

.card:has(.card-link:focus-visible) {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

.card :is(button, a:not(.card-link)) {
  position: relative;
  z-index: 1;
}
Why it works, the rules and the credits 4 reasons · 4 rules · support · credits

Why it works

  1. The pseudo-element belongs to the link and covers the card, so a click anywhere activates the link. It is a real link, so open in new tab and the status bar URL still work.
  2. The link text stays short. A screen reader announces "Quarterly report" and not the whole card.
  3. :has() moves the focus ring from the small link to the card it activates.
  4. Other controls sit above the overlay and keep their own clicks.

Rules

  1. No element between the card and the link may be positioned. The overlay would size itself to that element.
  2. The link's own ring is made transparent, never removed, as One focus ring with :focus-visible requires. Write the full 2px solid transparent. Safari ignores outline-color on its default ring.
  3. Text under the overlay cannot be selected. Accept it.
  4. Put hover styles on .card:hover, inside the query from Hover styles only where hover exists.

Support

Chrome
105
Firefox
121
Safari
15.4

Credits

Borrowed from: Heydon Pickering, Cards, for the stretched ::after · Stripe, for moving the focus ring to the card with :has()