Who it affects
Someone using a screen reader on a gym's class timetable lands on a clickable class card and hears it as one button, so the Book link inside it is never announced.
Someone using a keyboard tabs to the inner Book link, presses Enter, and the outer card's handler fires instead, opening the class details rather than booking.
Someone using a switch or voice control gets two overlapping targets with one name and cannot pick the inner one.
Broken versus fixed
Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.
The whole card is one button to a screen reader. The Book link inside it is swallowed into the button's name.
The card is plain content. The Book link is a separate, named link that keyboard and screen reader users can reach.
Show the markup behind both examples
<style>body{font-family:system-ui;margin:0;padding:16px}.card{border:1px solid #d0d7de;border-radius:8px;padding:12px;max-width:320px;display:flex;justify-content:space-between;align-items:center;cursor:pointer}.card:focus{outline:3px solid #1b5e9e}.book{background:#1b5e9e;color:#fff;padding:6px 12px;border-radius:6px;text-decoration:none}.book:focus{outline:3px solid #e07a1f}</style>
<div class="card" role="button" tabindex="0">
<span><strong>Spin</strong><br>6pm, Studio 2</span>
<a href="#book-spin" class="book">Book</a>
</div>
<p style="font-size:14px;color:#555">Click in this frame and press Tab twice: two stops, one name.</p>
<style>body{font-family:system-ui;margin:0;padding:16px}.card{border:1px solid #d0d7de;border-radius:8px;padding:12px;max-width:320px;display:flex;justify-content:space-between;align-items:center}.book{background:#1b5e9e;color:#fff;padding:6px 12px;border-radius:6px;text-decoration:none}.book:focus{outline:3px solid #e07a1f}</style>
<div class="card">
<span><strong>Spin</strong><br>6pm, Studio 2</span>
<a href="#book-spin" class="book">Book Spin, 6pm</a>
</div>
<p style="font-size:14px;color:#555">Click in this frame and press Tab: one stop, the link.</p>
Test it by hand in two minutes
- Press Tab through the timetable. If one card takes two Tab presses to get past, two controls are stacked, and the inner one is probably nested.
- Right click the inner control, choose Inspect, and walk up its ancestors in the Elements panel. If you meet a button, a role="button", a tab, a menuitem, an option or a switch before you leave the card, that is the problem.
- With a screen reader running, move to the card. If you hear one long button name that includes the word Book, the link is being swallowed.
- Press Enter on the inner control and watch which action fires.
The fix
<div role="button" tabindex="0" class="class-card">
Spin, 6pm, Studio 2
<a href="/book/spin-1800">Book</a>
</div>
<div class="class-card">
<h3>Spin, 6pm, Studio 2</h3>
<a href="/book/spin-1800">Book Spin, 6pm</a>
</div>
Decide what the one action is and make that the control. If the whole card should be clickable, stretch the link over the card with CSS rather than wrapping the card in a button.
Remove button inside a filter chip button
<button class="chip">Yoga <button class="remove">x</button></button>
<span class="chip">Yoga <button class="remove" aria-label="Remove Yoga filter">x</button></span>
The chip did not need to be a button; only the remove action does anything.
Whole card clickable with one link
<div role="button" tabindex="0" class="card">
<a href="/classes/yoga">Yoga</a>
<p>Tuesdays, 7pm</p>
</div>
<div class="card">
<a href="/classes/yoga" class="stretched">Yoga</a>
<p>Tuesdays, 7pm</p>
</div>
<style>.card{position:relative}.stretched::after{content:"";position:absolute;inset:0}</style>
The link's ::after covers the card, so a click anywhere follows the link, while the page has one control with one name.
Close button inside a tab
<div role="tab" tabindex="0">Monday <button>x</button></div>
<div class="tab-wrap">
<div role="tab" tabindex="0">Monday</div>
<button aria-label="Close Monday tab">x</button>
</div>
A tab's children are presentational, so the button inside it is lost. Put it beside the tab.
Common mistakes
- Adding tabindex="-1" to the inner control to hide the problem; it is still inside, and still unreachable for some people.
- Putting role="presentation" on the inner button; a focusable element cannot be presentational.
- Wrapping a whole card in a link and placing buttons inside it; browsers tolerate it, assistive technology does not.
- Fixing the double click with stopPropagation in JavaScript and leaving the structure as it was.
WCAG criteria this rule maps to
axe-core tags this rule as WCAG 2.2 level A. It is one of the checks for:
- 4.1.2 Name, Role, Value (level A). Official W3C text.
The scanner can see a focusable element inside a button, tab, switch, slider, option, menuitem or similar role whose children are treated as presentational. It does not flag a button inside a link, which is also wrong, so check those by hand. Automated tools find only part of the accessibility problems on a page; a pass on this rule is not a pass on the criterion.
Related rules
button-name
A button needs a name that assistive technology can announce.
A
Links must have text a screen reader can announce
link-name
Every link needs text that a screen reader can announce and a voice control user can say.
A
ARIA buttons, links and menu items need a name
aria-command-name
When you give an element role="button", role="link" or role="menuitem", you are promising assistive technology that it is a control.
A
Hidden elements must not contain anything you can Tab to
aria-hidden-focus
aria-hidden="true" tells screen readers to ignore an element, but it does not stop the Tab key reaching the links and buttons inside.