Skip to content
easeweb
Search topics

Buttons inside card links that also open the page

A product card is one link with its wishlist heart inside, so the link's name includes the button's words, and pressing the heart opens the product page too.

WCAG 2.2
SC 4.1.2A
Required by
Section 508 · EN 301 549 (EU)
Broken <a href="/mugs/speckled"> … <button aria-label="Add to wishlist">
The accessibility problem illustrated: No vision:Will this save the mug, or open it?
01

The problem

Screen reader users hear a card link whose name also claims the wishlist, and anyone who presses the heart is taken to the product page as well.

02

Try it

Save the Speckled mug with its heart, first with the mouse, then with Tab and Enter.

Does the list stay where it is, or does the mug’s page open? The browser’s Back arrow returns to the list.

Broken Can you fix this?

BrokenA card wrapped in one link, the heart inside it

A recreated demo. The broken version is intentionally inaccessible.

03

The fix

Put the heart beside the card's link, not inside it.

Before
BrokenA card wrapped in one link, the heart inside it
<ul class="cards">  <li class="card">    <a href="/mugs/speckled" class="card-link">      <img src="speckled-mug.webp" alt="">      <h3>Speckled mug</h3>      <p class="price">$24</p>      <button type="button" class="wish" aria-label="Add to wishlist" aria-pressed="false">        <svg aria-hidden="true">…</svg>      </button>    </a>  </li>  …</ul>
What is wrong3 points
  • The heart's words are part of the link's name, so the link is read as "Speckled mug $24 Add to wishlist".
  • Pressing the heart, with a mouse, a finger, Enter or Space, saves the mug and then opens the product page.
  • A screen reader is told about a button inside a link, which HTML doesn't allow, so what it says is up to each screen reader.
After

The heart beside the link

<ul class="cards">  <li class="card">    <a href="/mugs/speckled" class="card-link">      <img src="speckled-mug.webp" alt="">      <h3>Speckled mug</h3>      <p class="price">$24</p>    </a>    <button type="button" class="wish" aria-label="Add to wishlist" aria-pressed="false">      <svg aria-hidden="true">…</svg>    </button>  </li>  …</ul>
How it is fixed3 steps
  1. Move the button out of the a and put it after the link, inside the card. The link now holds the photo, the name and the price, and its name is "Speckled mug $24".
  2. Move position: relative from the link to the card, so the heart stays in the photo's corner. It looks exactly as it did.
  3. Leave the button's handler as it is. Pressing the heart now presses the heart alone, and the list stays where it was.

This fixes the Before and passes 4.1.2. The heart comes after the link in the source, so Tab reaches the link first and then the heart, the same order as before.

The patch that is not a fix
wish.addEventListener('click', (event) => {  event.preventDefault();  event.stopPropagation();  …});

04

Verify the fix

4 checks.

Test with: Keyboard, Screen reader, Mouse, Touch

0 of 4 checked

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the card or link component changes.

Limits

This guide covers a button placed inside a card's link. The reverse, a link inside a button, is the form axe reports, and is fixed the same way, by putting the two side by side. A link laid over a banner or card with no words of its own is a link with no name: see banner and image links with no name. Cards that each end in the same link text are covered in "Read more" links that all say the same thing, card actions that show only under the mouse in card actions that only appear on hover, and a link used to do a button's job in buttons announced as links that Space can't press. Test the complete journey with your target assistive technology.

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

EaseWeb service

Fixed it? Keep it from coming back.

I turn guides like this one into rules your coding agents follow and checks your CI runs. Then I retest by hand with a keyboard and screen readers, periodically.

See the service