Skip to content

fix(image-alt): allow whitespace alt on presentational images - #5218

Merged
WilcoFiers merged 1 commit into
developfrom
fix/5216-image-alt-whitespace-presentation
Jul 21, 2026
Merged

fix(image-alt): allow whitespace alt on presentational images#5218
WilcoFiers merged 1 commit into
developfrom
fix/5216-image-alt-whitespace-presentation

Conversation

@WilcoFiers

@WilcoFiers WilcoFiers commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix false positive on image-alt for <img alt=" " role="presentation|none"> by skipping the alt-space-value check when the resolved role is presentational
  • Conflict-resolution cases (e.g. focusable presentational images) still fail correctly via getRole
  • Leave aria-allowed-role as-is — whitespace alt on a presentational image remains a minor best-practice flag

Closes #5216

Test plan

  • Unit tests for alt-space-value (presentation/none pass; conflict resolution still fails)
  • Integration + virtual-rules cases for image-alt (#pass12, #pass13)
  • Confirm <img alt=" " role="presentation"> passes image-alt and still fails aria-allowed-role

Made with Cursor

Whitespace-only alt values were always failing image-alt via the
alt-space-value none check, even when role=presentation/none made
the image decorative. Skip that check when the resolved role is
presentation or none.

Closes #5216

Co-authored-by: Cursor <[email protected]>

@WilcoFiers WilcoFiers left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From Claude: Reviewed the fix. Clean, well-scoped change. Using getRole (rather than a raw role read) is the right call—conflict-resolution cases (focusable/globally-labelled presentational images) still fall back to the implicit img role and continue to fail correctly. Good coverage across unit, integration, and virtual-rules tests. No blocking concerns.

@WilcoFiers
WilcoFiers marked this pull request as ready for review July 20, 2026 11:46
@WilcoFiers
WilcoFiers requested a review from a team as a code owner July 20, 2026 11:46
Copilot AI review requested due to automatic review settings July 20, 2026 11:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an image-alt false positive where <img alt=" " role="presentation|none"> was incorrectly treated as failing due to the alt-space-value check, by exempting presentational images (while still failing conflict-resolution cases via resolved role logic).

Changes:

  • Update alt-space-value evaluation to ignore whitespace-only alt when the resolved role is presentation or none.
  • Add unit coverage for presentational/none role behavior and conflict-resolution behavior in alt-space-value.
  • Add integration + virtual-rule pass cases for image-alt with whitespace-only alt and presentational roles.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/integration/virtual-rules/image-alt.js Adds virtual-rule pass cases for whitespace-only alt when role is presentational.
test/integration/rules/image-alt/image-alt.json Extends the integration “passes” list to include the new presentational whitespace-alt cases.
test/integration/rules/image-alt/image-alt.html Adds fixtures for `<img alt=" " role="presentation
test/checks/label/alt-space-value.js Adds unit tests ensuring presentational/none roles don’t trigger the whitespace-alt failure, while conflict-resolution still does.
lib/checks/label/alt-space-value-evaluate.js Implements role-aware exemption for whitespace-only alt on presentational images.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 3 to +7
function altSpaceValueEvaluate(node, options, virtualNode) {
if (['presentation', 'none'].includes(getRole(virtualNode))) {
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sounds valid. Check it out?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't cause a noticeable performance problem. If we do find this is a problem I think the correct answer is to memoize getRole rather than trying to micro optimize where the function is called.

chutchins25
chutchins25 previously approved these changes Jul 21, 2026

@chutchins25 chutchins25 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work — clean, well-scoped fix.

Verified the key points:

  • alt-space-value is used only in image-alt's none array, so the exemption can't leak into other rules.
  • getRole applies conflict resolution, so focusable/globally-labelled "presentational" images resolve back to the implicit img role and still fail correctly — the added tabindex="0" unit test proves it.
  • img isn't in the presentation-inheritance chain, so there's no getInheritedRole ReferenceError risk here.

Coverage across unit, integration, and virtual-rules is good.

One optional, non-blocking note: our convention is to include an open Shadow DOM unit case for check changes. This particular branch isn't DOM-boundary sensitive for img, and the integration + virtual-rules cases already cover the behavior, so I don't consider it a blocker — flagging only for completeness.

(No comment on the getRole hot-path ordering — Copilot's inline suggestion already covers that and it's a reasonable micro-optimization.)

@chutchins25
chutchins25 dismissed their stale review July 21, 2026 13:14

Switching my Approve to a Comment so the getRole hot-path suggestion isn't lost — see updated review.

@chutchins25 chutchins25 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped fix. Verified the key points:

  • alt-space-value is used only in image-alt's none array, so the exemption can't leak into other rules.
  • getRole applies conflict resolution, so focusable/globally-labelled "presentational" images resolve back to the implicit img role and still fail correctly — the added tabindex="0" unit test proves it.
  • img isn't in the presentation-inheritance chain, so there's no getInheritedRole ReferenceError risk here.

Coverage across unit, integration, and virtual-rules is good.

Leaving this as a Comment rather than an Approve on one point: Copilot's inline suggestion to check alt before calling getRole is valid — getRole runs for every <img> and isn't cached, so gating it behind the whitespace check avoids the work in the common case. Worth applying (or explicitly deciding to skip) before merge.

Optional, non-blocking: our convention is to include an open Shadow DOM unit case for check changes. This branch isn't DOM-boundary sensitive for img and is already covered by integration + virtual-rules, so not a blocker — flagging only for completeness.

Comment on lines 3 to +7
function altSpaceValueEvaluate(node, options, virtualNode) {
if (['presentation', 'none'].includes(getRole(virtualNode))) {
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't cause a noticeable performance problem. If we do find this is a problem I think the correct answer is to memoize getRole rather than trying to micro optimize where the function is called.

@WilcoFiers
WilcoFiers merged commit c5dd0ef into develop Jul 21, 2026
25 checks passed
@WilcoFiers
WilcoFiers deleted the fix/5216-image-alt-whitespace-presentation branch July 21, 2026 16:53
WilcoFiers added a commit that referenced this pull request Aug 5, 2026
All notable changes to this project will be documented in this file. See
[commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version)
for commit guidelines.

##
[4.13.0](v4.12.1...v4.13.0)
(2026-08-05)

### Features

- **aria-actions:** add aria-actions to allowed ARIA attributes
([#5200](#5200))
([029655d](029655d)),
closes [#4584](#4584)
[#5199](#5199), references
[#5215](#5215)
[#5215](#5215)
- **aria-allowed-attr:** flag deprecated ARIA attributes as needs-review
([#5246](#5246))
([518f3cc](518f3cc)),
closes [#3341](#3341)
- **aria-prohibited-attr:** allow many elements to be named and disallow
label and body from being named
([#5259](#5259))
([d8b1ea5](d8b1ea5))
- **aria-roles:** add sectionheader and sectionfooter roles
([#5238](#5238))
([c36c109](c36c109)),
closes [#4734](#4734),
references [#4734](#4734)
- **aria/get-aria-value:** new function to get aria values of a node
([#5109](#5109))
([a7d8f3e](a7d8f3e)),
references [#5042](#5042)
- **aria/has-attr-value:** new function to check if node has aria value
([#5136](#5136))
([61f2624](61f2624)),
references [#5109](#5109)
- **aria:** support role=image as equivalent to role=img
([#5248](#5248))
([5aa8aaf](5aa8aaf)),
closes [#4656](#4656),
references [#5272](#5272)
- **checks/aria:** support ARIA element internals properties
([#5172](#5172))
([9b7f754](9b7f754))
- **checks/label:** support ARIA element internals properties
([#5170](#5170))
([21c5f8b](21c5f8b))
- **checks/navigation:** support ARIA element internals properties
([#5167](#5167))
([2c3a98f](2c3a98f))
- **commons/aria:** support ARIA element internals properties
([#5171](#5171))
([31f09e7](31f09e7))
- **commons/dom:** support ARIA element internals properties
([#5163](#5163))
([f0a12cf](f0a12cf))
- **commons/forms:** support ARIA element internals properties
([#5165](#5165))
([27a4686](27a4686))
- **commons/matches/fromPrimative:** deprecate in favor of correct
spelling ([#5270](#5270))
([31cfb2e](31cfb2e))
- **commons/text:** support ARIA element internals properties
([#5169](#5169))
([e841a33](e841a33))
- **commons/text:** support form-associated labels via element internals
([#5182](#5182))
([57cfe0a](57cfe0a)),
closes [#5045](#5045),
references [#5170](#5170)
[#5039](#5039)
[#5151](#5151)
[#5039](#5039)
- **dom/getResolvedRefs:** new function to get the resolved virtual
nodes of idrefs
([#5151](#5151))
([489cdea](489cdea)),
references [#5109](#5109)
- **element-internals:** enable ElementInternals by default
([#5284](#5284))
([2740d42](2740d42)),
closes [#5277](#5277)
- **i18n:** Add Swedish locale
([#5190](#5190))
([dcd13f2](dcd13f2)),
references [#5189](#5189)
- **matches:** add inSectioningContent, hasChild, and
isSummaryForDetails matches
([#5262](#5262))
([c47cdcd](c47cdcd))
- **rules:** support ARIA element internals properties
([#5168](#5168))
([065baf7](065baf7))
- **standards/ariaAttrs:** add caseInsensitive property for attributes
([#5224](#5224))
([bcd791c](bcd791c))

### Bug Fixes

- **aria-allowed-role:** allow roles on a non-details summary
([#5242](#5242))
([3bd9875](3bd9875)),
closes [#3911](#3911),
references [#3443](#3443)
[#3911](#3911)
- **aria-allowed-role:** restrict figure roles with child figcaption
([#5240](#5240))
([178a635](178a635)),
closes [#3443](#3443)
- **aria-prohibited-attr:** visible aria-labelledby requires review only
([#5285](#5285))
([fd6fa9f](fd6fa9f))
- **axe.d.ts:** make enabled property of RuleMetadata optional
([#5129](#5129))
([90fce18](90fce18))
- **color-contrast:** fix various stacking context bugs
([#5214](#5214))
([d5e5b04](d5e5b04)),
references [#8](#8)
[#5213](#5213)
- **gather-internals:** handle non-HTMLElement nodes
([#5161](#5161))
([06e84c3](06e84c3))
- **get-selector:** escape control characters in attribute selectors
([#5273](#5273))
([4b60ac5](4b60ac5)),
closes [#5204](#5204)
[#5204](#5204)
- **image-alt:** allow whitespace alt on presentational images
([#5218](#5218))
([c5dd0ef](c5dd0ef)),
closes [#5216](#5216)
- **landmark-unique:** exclude section/form with non-landmark roles from
landmark match
([#5085](#5085))
([c5fd013](c5fd013)),
closes [#4722](#4722)
[#5064](#5064)
- name the image role in role-img-alt and svg-img-alt metadata
([#5279](#5279))
([995a269](995a269)),
closes [#5272](#5272),
references [#5248](#5248)
[#5248](#5248)
- **standards:** update aria-errormessage and aria-details to be idrefs
([#5157](#5157))
([fb94f8a](fb94f8a))

This PR was opened by a robot 🤖 🎉
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.

image with a space in the alt and role=none should pass

4 participants