feat(aria): support role=image as equivalent to role=img - #5248
Conversation
|
Design note: this PR implements the synonym mechanism @WilcoFiers proposed on the issue rather than special-casing |
54be348 to
eee05ae
Compare
There was a problem hiding this comment.
After look at this pr and #5255, I think we should drop the whole synonym stuff as it can cause real issues in reporting the role back to the user (we'd have to remember that both these cases require getting the role the user used and not the one our code resolved it to). I'd rather our code was a bit messier in the [presentation, none].includes(role) checks in order to prevent confusing reporting.
Additionally, this pr also needs to add image to the html-elms spec anywhere the allowedRole allows img (e.g. embed)
Add the ARIA 1.3 image role (w3c/aria#1370) as an alias of img: it is a valid role sharing img's definition (spread from a common object) and is allowed wherever img is allowed in the html-elms spec (embed, iframe, svg). The role-img-alt selector matches both roles via :is(). This avoids a synonym-resolution mechanism, so the role the author used is preserved when reporting an unallowed role. Closes #4656
eee05ae to
229e59f
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates axe-core’s ARIA role handling and related rule/test coverage to treat role="image" as valid (aligned with ARIA 1.3’s synonym for img) and to ensure existing rules that key off authored roles (like role-img-alt) also cover image.
Changes:
- Add an
imagerole definition alongsideimgin ARIA role standards. - Allow
imagein the relevant ARIA-in-HTML element role allowlists (e.g.,embed,iframe,object). - Extend
role-img-altrule matching and integration/virtual-rule/unit tests to coverrole="image"pass/fail scenarios.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/integration/virtual-rules/role-img-alt.js | Adds virtual-rule test cases for role="image" passing with an accessible name and failing without one. |
| test/integration/rules/role-img-alt/role-img-alt.json | Updates expected pass/violation targets to include role="image" fixtures. |
| test/integration/rules/role-img-alt/role-img-alt.html | Adds role="image" pass and violation fixtures for the role-img-alt integration test. |
| test/integration/rules/aria-roles/aria-roles.json | Adds expected pass target for the new role="image" fixture. |
| test/integration/rules/aria-roles/aria-roles.html | Adds a role="image" element to validate that it’s treated as a valid ARIA role. |
| test/integration/rules/aria-allowed-role/aria-allowed-role.json | Adds expected pass target for an element using role="image". |
| test/integration/rules/aria-allowed-role/aria-allowed-role.html | Adds an embed fixture with role="image" to validate allowed-role behavior. |
| test/commons/aria/is-valid-role.js | Adds a unit test asserting image is considered a valid role. |
| test/commons/aria/get-element-unallowed-roles.js | Adds unit tests ensuring role="image" is allowed where appropriate and reported as-authored when unallowed. |
| lib/standards/html-elms.js | Adds image to element allowedRoles lists where img is explicitly allowlisted. |
| lib/standards/aria-roles.js | Introduces an image role entry mirroring img via a shared definition object. |
| lib/rules/role-img-alt.json | Expands the selector to match both [role='img'] and [role='image']. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
straker
left a comment
There was a problem hiding this comment.
Just a minor nit about duplicate comments
The shared imgRole definition already documents the img/image link, so the duplicate comment on the image entry is unneeded. Addresses review feedback.
|
Done. Dropped the synonym mechanism — |
…ge-synonym # Conflicts: # test/integration/rules/aria-roles/aria-roles.json
Dismissing as stale — this predates the rework. All 5 inline suggestions from this review are resolved, and your later notes are addressed: the synonym mechanism was dropped (image is now a first-class role sharing img's definition), image was added to every html-elms allowedRoles list that includes img (embed, iframe, svg), and the duplicate comment was removed. The branch is now also merged up to date with develop. Re-requesting review.
There was a problem hiding this comment.
I believe svg-img-alt also needs to be updated to select [role=image}, but this will require asking @WilcoFiers if the image role also applies to svg elements (it's a different spec).
<svg role="image"> matched neither svg-img-alt (selector) nor role-img-alt (html-namespace-matches), so its accessible name went unchecked. aria-roledescription's supportedRoles listed img but not image.
The synonym mechanism was dropped from this PR, so the tests no longer call image a synonym role.
Good catch. It was worse than a missing selector — I've updated @WilcoFiers — could you confirm? If While sweeping for other role-name allowlists that mention Also in this push: |
) Follow-up to #5248. Since that change, `role-img-alt` and `svg-img-alt` match both `role="img"` and `role="image"` (via `:is([role='img'], [role='image'])`), but their metadata still named only `img` — so an author who wrote `<div role="image">` got a message referencing a role they didn't write. ## Changes - **`role-img-alt`** — description/help now name `[role="img"]` **and** `[role="image"]`. - **`svg-img-alt`** — description/help now include the `image` role alongside `img`/`graphics-document`/`graphics-symbol`. - Regenerated `locales/_template.json` and `doc/rule-descriptions.md` (via `pnpm run build`). ## Notes - Editing these four strings **invalidates the existing translations** for those keys until they're retranslated — this was deliberately deferred from #5248 and is expected. Only `locales/_template.json` is regenerated here; the translated `locales/*.json` files are left for the translation workflow. - A **matching `axe-rule-help` update** (the `role-img-alt`/`svg-img-alt` help pages) is a follow-up, tracked separately. Closes #5272
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 🤖 🎉
Adds support for
role="image"as a valid role equivalent torole="img"(ARIA 1.3, w3c/aria#1370).What & why
ARIA 1.3 adds
imageas an alias ofimgwith an identical mapping; axe previously treatedrole="image"as an invalid role.Per the review discussion, this does not use a synonym-normalization mechanism (which would force every reporting path to remember the role the author used vs. the one our code resolved it to). Instead,
imageis a first-class role that sharesimg's definition and is allowed whereverimgis:imageis added tolib/standards/aria-roles.js, spread from a sharedimgRoleobject so it stays in sync withimg.imageis added to everyhtml-elmsallowedRoleslist that includesimg(embed,iframe,object).svgneeds no change — it isallowedRoles: true, so it already accepts any role.role-img-altandsvg-img-altselectors match both roles via:is([role='img'], [role='image']). Without thesvg-img-altchange,<svg role="image">matched neither rule —svg-img-alt's selector omitted it androle-img-altis gated byhtml-namespace-matches— so axe accepted the role and then never checked for an accessible name.imageis added to thearia-roledescriptioncheck'ssupportedRoles, sorole="image"passes whererole="img"does.Because there is no synonym resolution, an unallowed
role="image"reportsimage(the role the author used), notimg.Tests
is-valid-role/aria-rolesintegration —imageis a valid role.get-element-unallowed-roles—imageallowed onsvg/embed/object/iframe; reported as authored when unallowed (e.g. onhr).role-img-altintegration + virtual-rules — matchesrole="image".svg-img-altintegration + virtual-rules — matchesrole="image".aria-roledescription—role="image"passes against the check's defaultsupportedRoles.7d6734and23a2a8still pass with the broadened selectors.Not in scope
role-img-altandsvg-img-altmetadata still name onlyimgin theirdescription/helpstrings. Updating them regenerateslocales/_template.json, invalidates those translations, and needs a matching axe-rule-help change — tracked in #5272.Closes #4656