Skip to Content
Component ExamplesCards

Overview

Cards are ubiquitous on the modern web — blog posts, product listings, dashboards. The most common accessibility mistake is wrapping the entire card in a single <a> tag. This forces screen readers to read every piece of content inside the card as one giant link name, making it nearly impossible to parse. The correct approach uses a heading link with a CSS ::after pseudo-element to extend the clickable area to the full card, giving users a concise link name while preserving full-card clickability for mouse users.

WCAG Criteria:

Key requirements:

  • Never wrap an entire card in a single <a> — screen readers concatenate all content as the link name
  • Use a heading link with a ::after pseudo-element to extend the click target to the full card
  • Use <article> for semantic card content
  • Each card must have a heading element
  • When cards have multiple actions, position action buttons above the pseudo-element layer with position: relative and z-index

Block Link Card

Inaccessible
<!-- Entire card wrapped in <a> — SR reads all content as one link name --> <a href="/intro-react" class="card"> <h3>Introduction to React</h3> <p>Learn the basics of React including components, state, and props for building modern user interfaces.</p> <span>5 min read</span> </a>
Accessible
<!-- Heading link with ::after pseudo-element extends click target --> <article class="card"> <h3> <a href="/intro-react" class="card-link">Introduction to React</a> </h3> <p>Learn the basics of React including components, state, and props for building modern user interfaces.</p> <span>5 min read</span> </article> <style> .card { position: relative; } .card-link::after { content: ''; position: absolute; inset: 0; } </style>
Live Preview

Introduction to React

Learn the basics of React including components, state, and props for building modern user interfaces.

5 min read

What’s wrong with wrapping the card in <a>?

  • Screen readers announce the entire card content as a single link: “Introduction to React Learn the basics of React including components, state, and props for building modern user interfaces. 5 min read, link” — a wall of text
  • The heading, description, and meta are no longer independent elements — they all collapse into one link name
  • Users cannot select text inside the card
  • The heading loses its semantic role within the link context in some screen readers

How the ::after technique works:

  • The link sits inside the <h3>, giving it a concise accessible name: “Introduction to React”
  • The ::after pseudo-element on the link is positioned absolutely over the entire card (inset: 0)
  • Mouse users can click anywhere on the card and follow the link
  • Screen reader users hear a properly scoped link name

What the screen reader announces:

VersionAnnouncement
Inaccessible (whole card as <a>)“Introduction to React Learn the basics of React including components, state, and props for building modern user interfaces. 5 min read, link”
Accessible (heading link + ::after)“Introduction to React, link” (heading) then “Learn the basics of React…” (paragraph) and “5 min read” (text) as separate elements

Card with Multiple Actions

Card with Multiple Actions

Inaccessible
<!-- No heading, no button labels — SR users get no context --> <div class="card"> <p><b>Introduction to React</b></p> <p>Learn the basics of React including components, state, and props.</p> <div class="actions"> <button><svg><!-- save icon --></svg></button> <button><svg><!-- share icon --></svg></button> </div> </div>
Live Preview

Introduction to React

Learn the basics of React including components, state, and props.

Accessible
<!-- Semantic heading, labeled buttons above the pseudo-element layer --> <article class="card"> <h3> <a href="/intro-react" class="card-link">Introduction to React</a> </h3> <p>Learn the basics of React including components, state, and props.</p> <div class="actions"> <button aria-label="Save" class="action-btn"> <svg aria-hidden="true"><!-- save icon --></svg> </button> <button aria-label="Share" class="action-btn"> <svg aria-hidden="true"><!-- share icon --></svg> </button> </div> </article> <style> .card { position: relative; } .card-link::after { content: ''; position: absolute; inset: 0; } .action-btn { position: relative; z-index: 1; /* sits above the ::after layer */ } </style>
Live Preview

Introduction to React

Learn the basics of React including components, state, and props.

What’s wrong with the inaccessible version?

  • No heading — screen reader users have no way to quickly identify or navigate to the card
  • Bold text (<b>) is visual only; it does not convey structure to assistive technology
  • Icon buttons have no accessible names — screen readers announce just “button” with no indication of what the button does
  • SVGs inside buttons are not hidden with aria-hidden, so some screen readers may attempt to announce SVG markup
  • The card uses a <div> instead of <article>, providing no semantic meaning

How the multiple-actions pattern works:

  • The heading link’s ::after pseudo-element covers the full card area, making the entire card clickable
  • Action buttons use position: relative and z-index: 1 to sit above the pseudo-element layer, so they remain independently clickable
  • Each button has an aria-label providing its purpose (“Save”, “Share”)
  • SVGs are marked aria-hidden="true" since the aria-label provides the accessible name

What the screen reader announces:

VersionAnnouncement
Inaccessible (no heading, no labels)“Introduction to React” (bold text, no structure), “button” (no name), “button” (no name)
Accessible (heading + labeled actions)“heading level 3, Introduction to React, link”, “Save, button”, “Share, button”

Resources