Summary
While investigating a false-positive report on the empty_button rule (unrelated issue, an Elementor sticky-header spacer clone), I found a second, separate gap worth a product decision before touching it.
The gap
button-is-empty.js checks for an accessible name via a chain of checks including:
// Check for an image with alt text
const images = node.querySelectorAll( 'img' );
for ( const img of images ) {
if ( img.getAttribute( 'alt' )?.trim() ) {
return false;
}
}
querySelectorAll('img') only searches descendants of the button-role node — it never checks the node itself. So this only covers <button><img alt="..."></button>. It does nothing for:
<img role="button" alt="Submit form" src="submit.png">
Here the [role="button"] selector match is the <img>, so the rule flags it as an empty button despite having a clear alt attribute, since nothing checks the node's own alt.
Reproduced with a scratch jest test against the real rule/check:
<img role="button" alt="Submit form" src="submit.png">
→ empty_button reports 1 violation.
Open question: should we support this at all?
My initial take: probably not worth special-casing. role="button" repurposes an <img> as an interactive control, but alt is defined by the HTML spec as a text alternative for the image content, not as an accessible name for an arbitrary ARIA role slapped onto it. A real <button> element has no alt attribute at all — it's not part of its accepted content model. So <img role="button" alt="..."> is already a spec-questionable pattern (should be a real <button>, or at minimum use aria-label), and teaching the empty-button check to treat alt as a valid accessible-name source for arbitrary button-role elements risks legitimizing/encouraging non-conformant markup rather than flagging it.
Counter-consideration: alt does participate in the accessible name computation for elements with role="button" per the accname spec (img's alt is used in step 2C regardless of role), so a screen reader will actually announce the alt text as the button's name — meaning today's flag is a false positive from a pure "does this have an accessible name" standpoint, even though the markup pattern itself is questionable.
Options
- Leave as-is — rule stays strict,
img[role="button"] without other accessible-name attributes (aria-label, title, etc.) continues to be flagged. Push authors toward aria-label or a real <button> instead.
- Add the same node-self-alt check used for
input[value] (button-is-empty.js:77) so img[role="button"][alt] passes, matching actual accname computation.
- Flag it as a different rule/warning (e.g. "use a native button instead of an image with role=button") separate from the empty-button check, while still treating
alt as satisfying the accessible-name requirement.
Leaning toward option 1 for now, but opening this for discussion before deciding.
Repro
document.body.innerHTML = '<img role="button" alt="Submit form" src="submit.png">';
const results = await axe.run( document.body, { runOnly: [ 'empty_button' ] } );
// results.violations.length === 1
Summary
While investigating a false-positive report on the
empty_buttonrule (unrelated issue, an Elementor sticky-header spacer clone), I found a second, separate gap worth a product decision before touching it.The gap
button-is-empty.jschecks for an accessible name via a chain of checks including:querySelectorAll('img')only searches descendants of the button-role node — it never checks the node itself. So this only covers<button><img alt="..."></button>. It does nothing for:Here the
[role="button"]selector match is the<img>, so the rule flags it as an empty button despite having a clearaltattribute, since nothing checks the node's ownalt.Reproduced with a scratch jest test against the real rule/check:
→
empty_buttonreports 1 violation.Open question: should we support this at all?
My initial take: probably not worth special-casing.
role="button"repurposes an<img>as an interactive control, butaltis defined by the HTML spec as a text alternative for the image content, not as an accessible name for an arbitrary ARIA role slapped onto it. A real<button>element has noaltattribute at all — it's not part of its accepted content model. So<img role="button" alt="...">is already a spec-questionable pattern (should be a real<button>, or at minimum usearia-label), and teaching the empty-button check to treataltas a valid accessible-name source for arbitrary button-role elements risks legitimizing/encouraging non-conformant markup rather than flagging it.Counter-consideration:
altdoes participate in the accessible name computation for elements withrole="button"per the accname spec (img's alt is used in step 2C regardless of role), so a screen reader will actually announce the alt text as the button's name — meaning today's flag is a false positive from a pure "does this have an accessible name" standpoint, even though the markup pattern itself is questionable.Options
img[role="button"]without other accessible-name attributes (aria-label,title, etc.) continues to be flagged. Push authors towardaria-labelor a real<button>instead.input[value](button-is-empty.js:77) soimg[role="button"][alt]passes, matching actual accname computation.altas satisfying the accessible-name requirement.Leaning toward option 1 for now, but opening this for discussion before deciding.
Repro