Wrapping an entire card in a single anchor tag has well-documented problems. Screen readers struggle with the flattened content, and users lose the ability to select text or interact with individual elements. But the pattern itself isn't necessarily the culprit — the implementation might be.

Before diving into solutions, it's worth defining what a good card component should do:

  • The whole card should be clickable.
  • It should support multiple links inside.
  • Content must remain semantic for assistive technology.
  • Text should be selectable like normal links.
  • Right-click actions and keyboard shortcuts should work.
  • All elements should be focusable when tabbing.

That's a demanding list — and there's no browser-native card widget to fall back on. Here's a look at the common approaches and whether they hold up.

The Obvious Approach: Wrap Everything in an Anchor

The simplest method is to put the entire card's HTML inside a single <a> tag:

<a href="/">
  <!-- Card markup -->
</a>

This gives us a fully clickable area that respects right-click and keyboard shortcuts. But it falls short everywhere else:

  • No nested links are possible.
  • Assistive technology reads everything as one flattened string, often starting with the timestamp rather than meaningful content.
  • Text selection is impossible.

Instead of wrapping the whole card, only the content that should be linked gets an anchor:

<article class="card">
  <time datetime="2020-03-20">Mar 20, 2020</time>
  <h2><a href="https://css-tricks.com/a-complete-guide-to-calc-in-css/" class="main-link">A Complete Guide to calc() in CSS</a></h2>
  <p>
    In this guide, let’s cover just about everything there is to know about this very useful function.
  </p>
  <a class="author-name" href="https://css-tricks.com/author/chriscoyier/" target="_blank">Chris Coyier</a>
    <div class="tags">
      <a class="tag" href="https://css-tricks.com/tag/calc/" >calc</a>
    </div>
</article>

This approach fixes most of the accessibility issues:

  • Multiple links are supported.
  • Content stays semantic.
  • Text is selectable.
  • Right-click and keyboard shortcuts work.
  • Tab order is preserved.

The tradeoff is obvious: the entire card is no longer clickable. That's the core feature users expect from a card, so we're not done yet.

The Pseudo-Element Trick

Some attempts overlay an absolutely positioned ::before or ::after element stretched across the card. The pseudo-element becomes the click target, positioned above the main card area while text stays selectable underneath.

This creates a new set of problems. The pseudo-element sits above all content, so it blocks clicks on any other links inside the card. Layering text above it restores text interaction but breaks card-level clicks on those areas. You're stuck choosing between a fully clickable card and supporting secondary interactions.

JavaScript as a Progressive Enhancement

A more complete solution builds on the semantic approach — keeping real links for content — and uses JavaScript to make the whole card clickable when appropriate.

Add a click event listener to the card element and trigger the main link when the card is clicked:

const card = document.querySelector(".card")
const mainLink = document.querySelector('.main-link')


card.addEventListener("click", handleClick)


function handleClick(event) {
  mainLink.click();
}

That reintroduces the text-selection problem. A quick fix uses window.getSelection(), a Web API that returns the user's current selection or caret position as a Selection object. Converting it to a string tells us whether anything is selected:

const isTextSelected = window.getSelection().toString()

The check slots into the click handler, so clicks on empty space navigate while text selection remains intact:

const card = document.querySelector(".card")
const mainLink = document.querySelector('.main-link')


card.addEventListener("click", handleClick)


function handleClick(event) {
  const isTextSelected = window.getSelection().toString();
  if (!isTextSelected) {
    mainLink.click();
  }
}

There's one remaining gotcha: events firing twice when clicking on elements that already have handlers — like secondary links or buttons inside the card. Stop event propagation on those elements to prevent the card's handler from also firing:

// You might want to add common class like 'clickable' on all elements and use that for the query selector.
const clickableElements = Array.from(card.querySelectorAll("a"));
clickableElements.forEach((ele) =>
  ele.addEventListener("click", (e) => e.stopPropagation())
);

With these few lines, the card meets every requirement:

  • The whole area is clickable.
  • Nested links work.
  • Content is semantic.
  • Text is selectable.
  • Right-click and keyboard interactions function normally.
  • Tab focus moves through elements in order.

This pattern still raises open questions. What happens when the card shows a post excerpt followed by a "Read More" link — should that be the main link? And where do images fit in the hierarchy? Deeper explorations of card UI patterns are available from Heydon Pickering, Adrian Roselli, and Dave Rupert, along with the original critique of block links by Chris Coyier.