Skip to content

empty_button rule: should we support alt on img[role="button"]? #1866

Description

@pattonwebz

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

  1. 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.
  2. 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.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions