Skip to content

BUGFIX: template-require-valid-alt-text — reject empty-string aria-label/labelledby/alt on <input type=image>, <object>, <area>#2730

Draft
johanrd wants to merge 5 commits intoember-cli:masterfrom
johanrd:fix/alt-text-empty-aria-label
Draft

BUGFIX: template-require-valid-alt-text — reject empty-string aria-label/labelledby/alt on <input type=image>, <object>, <area>#2730
johanrd wants to merge 5 commits intoember-cli:masterfrom
johanrd:fix/alt-text-empty-aria-label

Conversation

@johanrd
Copy link
Copy Markdown
Contributor

@johanrd johanrd commented Apr 21, 2026

Summary

  • Premise 1: An empty-string aria-label="" contributes no accessible name per ACCNAME 1.2 §4.3.2 step 2D: "…has an aria-label attribute whose value is not undefined, not the empty string, nor, when trimmed of whitespace, is not the empty string." Empty aria-labelledby="" likewise contributes no name per ACCNAME 1.2 §4.3.1 step 2B, which requires the attribute to reference at least one valid IDREF — an empty value references none. Empty title="" is not handled by any ACCNAME step that would assign a name (step 2I is the Tooltip/title step, and it does not match an empty string). For <input type="image">, <object>, and <area>, an accessible name is required per their HTML spec / HTML-AAM conformance requirements.
  • Premise 2: Today our rule checks only for the presence of any fallback attribute, not its value — so <input type="image" aria-label="" />, <object aria-labelledby="" />, and <area title="" /> are accepted.
  • Conclusion: Reject empty / whitespace-only aria-label, aria-labelledby, and title on <input type="image">, <object>, and <area>. Dynamic values (mustache, concat) stay accepted — we can't know at lint time whether they resolve to empty.

Fix: add hasNonEmptyTextAttr() that requires a static value to be non-whitespace.

<img>'s alt="" is unchanged — an empty alt on <img> is spec-defined as a marker for decorative images.

Nine new invalid tests (3 elements × 3 fallback attrs) cover the fix.

Prior art

Verified each peer in source:

Plugin Rule Behavior
jsx-a11y alt-text ariaLabelHasValue (lines 37-46) rejects undefined and length === 0. Called for object, area, and input[type="image"]. Does NOT trim whitespace (differs from our rule which does).
vuejs-accessibility alt-text hasAriaLabel(node) + getElementAttributeValue truthiness. Empty strings are falsy → flagged.
@angular-eslint/template alt-text Existence-onlyisValidObjectNode/isValidAreaNode/isValidInputNode all return true on mere attribute-name presence (same bug class this PR fixes).
lit-a11y alt-text Existence-only via !elementHasAttribute(...). Also: no <object> or <area> handler — rule only checks img, input[type=image], role="img".

Audit fixture

Translated peer-plugin test fixture at tests/audit/alt-text/peer-parity.js. Not wired into the default test run.

johanrd added 3 commits April 21, 2026 07:50
…aria-labelledby/alt

Before: for <input type="image">, <object>, and <area>, the rule checked
only for the PRESENCE of an accessible-name fallback attribute
(aria-label / aria-labelledby / alt / title). An empty-string value
provides no accessible name but slipped past.

Fix: add hasNonEmptyTextAttr() that requires the attribute's static
value to be non-whitespace. Dynamic values (mustache, concat) remain
accepted — we can't tell at lint time whether they resolve to empty.

<img>'s alt handling is unchanged — alt="" is still valid there
(spec-defined marker for decorative images).

Nine new invalid tests cover the three elements × three fallback attrs.
Translates 41 cases from peer-plugin rules:
  - jsx-a11y alt-text
  - vuejs-accessibility alt-text
  - lit-a11y alt-text

Fixture documents parity after this fix:
  - Empty-string aria-label/aria-labelledby on <object>, <area>, and
    <input type=image> is now flagged (reusing existing objectMissing /
    areaMissing / inputImage messageIds).

Remaining divergences (<img alt role=presentation> accepting non-empty
alt in jsx-a11y, <img aria-label> without alt) are annotated inline.
johanrd added 2 commits April 22, 2026 14:21
…verage (Copilot review)

- JSDoc for hasNonEmptyTextAttr() rewritten: no longer overstates the
  guarantee for dynamic values, and notes that aria-labelledby IDREFs
  are not validated.
- Added invalid-case coverage for whitespace-only aria-label /
  aria-labelledby / title — ACCNAME 1.2 §4.3.2 step 2D.
- hasNonEmptyTextAttr() already trims static values, so the new
  whitespace-only cases flag without further rule changes.
@johanrd johanrd force-pushed the fix/alt-text-empty-aria-label branch from c4885d4 to 82022bb Compare April 22, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant