Implementing accessible clickable cards

A common web pattern is the clickable card that navigates users to more detailed content.

I used to wrap the card in a link like this to make it clickable:

<a href="/link" target="_blank>
	<section className="property-card">
		<h2>Luxury living packed into a convenient apartment</h2>
		<span>1 bed | 1 bath  | 1 carpark spot</span>
		<p>Enjoy the best of Brisbane from this riverside haven. See if this studio apartment in South Brisbane is right for you.</p>
	</section>
</a>

Until I covered Scrimba's accessible development module in their front-end development career path where I learnt how the above works but…

Why wrapping the card in a link can be an issue

When an entire card's content is placed in a link, a long string of text content will be read out before the person is told that it is a link.

Screen readers can extract and list links on a page. When a link contains a large block of text from a card, it becomes a long, less digestible entry in that list.

Meaningful, standalone link text (e.g., "Explore accessibility resources") is preferred over lengthy card content.

Place links within the card instead

Step 1 in HTML

  • Place an empty link at the end of card content
  • Add aria-label for screen readers
	<section className="property-card">
		<h2>Luxury living packed into a convenient apartment</h2>
		<span>1 bed | 1 bath  | 1 carpark spot</span>
		<p>Enjoy the best of Brisbane from this riverside haven. See if this studio apartment in South Brisbane is right for you.</p>
		**<a 
			href="/link" 
			target="_blank" 
			className="property-card-link" 
			aria-label="Learn more about this studio apartment in South Brisbane">
		</a>**
	</section>

Step 2 with Tailwind CSS

  • Size width and height of the link (property-card-link) over the entire card.
  • The :after pseudo-element with content: "" and absolute positioning makes the entire card clickable, sitting on top of the content (z-10).
  • The cursor-pointer on the card indicates clickability.
  • Test focus states (I removed it from the link because the project-card handles the focus state)
.property-card {
	@apply relative bg-white hover:bg-stone-50 cursor-pointer;
	
	&:has(:focus-visible) {
		@apply outline outline-2 outline-offset-4;
	}
}

.property-card-link {
@apply w-full h-full;

	&:after {
		@apply absolute inset-0 z-10;
		content: "";
	}
	&:focus-visible {
		@apply outline-none;
	}
}
All notes