Skip to content

feat(aria): support role=image as equivalent to role=img - #5248

Merged
straker merged 6 commits into
developfrom
chut/4656-role-image-synonym
Aug 3, 2026
Merged

feat(aria): support role=image as equivalent to role=img#5248
straker merged 6 commits into
developfrom
chut/4656-role-image-synonym

Conversation

@chutchins25

@chutchins25 chutchins25 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Adds support for role="image" as a valid role equivalent to role="img" (ARIA 1.3, w3c/aria#1370).

What & why

ARIA 1.3 adds image as an alias of img with an identical mapping; axe previously treated role="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, image is a first-class role that shares img's definition and is allowed wherever img is:

  • image is added to lib/standards/aria-roles.js, spread from a shared imgRole object so it stays in sync with img.
  • image is added to every html-elms allowedRoles list that includes img (embed, iframe, object). svg needs no change — it is allowedRoles: true, so it already accepts any role.
  • The role-img-alt and svg-img-alt selectors match both roles via :is([role='img'], [role='image']). Without the svg-img-alt change, <svg role="image"> matched neither rule — svg-img-alt's selector omitted it and role-img-alt is gated by html-namespace-matches — so axe accepted the role and then never checked for an accessible name.
  • image is added to the aria-roledescription check's supportedRoles, so role="image" passes where role="img" does.

Because there is no synonym resolution, an unallowed role="image" reports image (the role the author used), not img.

Tests

  • is-valid-role / aria-roles integration — image is a valid role.
  • get-element-unallowed-rolesimage allowed on svg/embed/object/iframe; reported as authored when unallowed (e.g. on hr).
  • role-img-alt integration + virtual-rules — matches role="image".
  • svg-img-alt integration + virtual-rules — matches role="image".
  • aria-roledescriptionrole="image" passes against the check's default supportedRoles.
  • ACT specs 7d6734 and 23a2a8 still pass with the broadened selectors.

Not in scope

role-img-alt and svg-img-alt metadata still name only img in their description/help strings. Updating them regenerates locales/_template.json, invalidates those translations, and needs a matching axe-rule-help change — tracked in #5272.

Closes #4656

@chutchins25

Copy link
Copy Markdown
Contributor Author

Design note: this PR implements the synonym mechanism @WilcoFiers proposed on the issue rather than special-casing image throughout — see #4656 (comment). Per the later discussion (support demonstrated by @scottaohara), image ships as a fully valid role, not unsupported. The follow-up to migrate none/presentation onto the same synonym field is tracked in #5249.

Comment thread lib/rules/role-img-alt.json Outdated
Comment thread lib/standards/aria-roles.js Outdated
Comment thread lib/commons/aria/get-explicit-role.js Outdated
Comment thread lib/commons/aria/get-element-unallowed-roles.js Outdated
@chutchins25
chutchins25 force-pushed the chut/4656-role-image-synonym branch 2 times, most recently from 54be348 to eee05ae Compare July 29, 2026 16:42

@straker straker 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.

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
@chutchins25
chutchins25 force-pushed the chut/4656-role-image-synonym branch from eee05ae to 229e59f Compare July 29, 2026 17:00
chutchins25 added a commit that referenced this pull request Jul 29, 2026
Extract the none role definition into a shared object and spread it into
both the none and presentation roles so they stay in sync. Per the
discussion on #5248, this drops the earlier synonym approach and keeps
the existing [none, presentation] role checks as-is.

Closes #5249
@chutchins25
chutchins25 marked this pull request as ready for review July 29, 2026 17:43
@chutchins25
chutchins25 requested a review from a team as a code owner July 29, 2026 17:43
Copilot AI review requested due to automatic review settings July 29, 2026 17:43

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

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 image role definition alongside img in ARIA role standards.
  • Allow image in the relevant ARIA-in-HTML element role allowlists (e.g., embed, iframe, object).
  • Extend role-img-alt rule matching and integration/virtual-rule/unit tests to cover role="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.

Comment thread lib/standards/aria-roles.js Outdated

@straker straker 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.

Just a minor nit about duplicate comments

Comment thread lib/standards/aria-roles.js Outdated
@chutchins25
chutchins25 marked this pull request as draft July 29, 2026 20:18
The shared imgRole definition already documents the img/image link, so
the duplicate comment on the image entry is unneeded.

Addresses review feedback.
@chutchins25

Copy link
Copy Markdown
Contributor Author

Done. Dropped the synonym mechanism — image is now a first-class role sharing img's definition, and I added image to every html-elms allowedRoles that allowed img (embed, iframe, svg). role-img-alt matches both via :is(). Since there's no resolution step, the authored role is what gets reported. Updated the PR description to match.

…ge-synonym

# Conflicts:
#	test/integration/rules/aria-roles/aria-roles.json
@chutchins25 chutchins25 changed the title feat(aria): support role=image as a synonym for role=img feat(aria): support role=image as equivalent to role=img Jul 30, 2026
@chutchins25
chutchins25 dismissed straker’s stale review July 30, 2026 15:15

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.

@chutchins25
chutchins25 requested a review from straker July 30, 2026 15:15
@chutchins25
chutchins25 marked this pull request as ready for review July 30, 2026 15:15

@straker straker 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.

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.
@chutchins25

Copy link
Copy Markdown
Contributor Author

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).

Good catch. It was worse than a missing selector — <svg role="image"> matched neither rule: svg-img-alt's selector omitted it, and role-img-alt is gated by html-namespace-matches. Since svg is allowedRoles: true, axe reported the role as allowed and then never checked for an accessible name.

I've updated svg-img-alt to :is([role='img'], [role='image']) with integration and virtual-rule coverage. My reading of the spec question: SVG-AAM maps SVG elements, and role values come from ARIA, so image should be as valid on <svg> as img is. Both ACT specs (7d6734, 23a2a8) still pass.

@WilcoFiers — could you confirm? If image should not apply to SVG, the fix is the opposite of what I've done: svg is allowedRoles: true today, so axe accepts <svg role="image"> either way, and we'd need to give svg an explicit allowlist to reject it. Happy to flip it.

While sweeping for other role-name allowlists that mention img, I also found the aria-roledescription check's supportedRoles — it listed img but not image, so role="image" returned incomplete where role="img" passes. Added. (lookup-table.js mentions img too, but it's @deprecated.)

Also in this push: image coverage for object/iframe in get-element-unallowed-roles, and the leftover "synonym" wording dropped from the test names now that the mechanism is gone. Rule message wording is tracked separately in #5272.

@chutchins25
chutchins25 deleted the chut/4656-role-image-synonym branch July 31, 2026 12:50
@chutchins25
chutchins25 restored the chut/4656-role-image-synonym branch July 31, 2026 12:51
@chutchins25 chutchins25 reopened this Jul 31, 2026
@straker
straker merged commit 5aa8aaf into develop Aug 3, 2026
42 of 45 checks passed
@straker
straker deleted the chut/4656-role-image-synonym branch August 3, 2026 15:38
straker pushed a commit that referenced this pull request Aug 3, 2026
)

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
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.

Support new role=image as synonym for role=img

3 participants