Skip to content

fix(template-require-mandatory-role-attributes): use axobject-query for semantic-role exemptions#2725

Draft
johanrd wants to merge 4 commits intoember-cli:masterfrom
johanrd:fix/role-required-aria-checkbox-switch
Draft

fix(template-require-mandatory-role-attributes): use axobject-query for semantic-role exemptions#2725
johanrd wants to merge 4 commits intoember-cli:masterfrom
johanrd:fix/role-required-aria-checkbox-switch

Conversation

@johanrd
Copy link
Copy Markdown
Contributor

@johanrd johanrd commented Apr 21, 2026

Premise

Per WAI-ARIA APG and HTML-AAM, a native HTML element that implements a given ARIA role implicitly supplies that role's required ARIA state. Classic example: <input type="checkbox" role="switch"> — the native checked attribute provides the aria-checked state that role="switch" requires. Authors shouldn't need to add aria-checked redundantly.

Problem

The rule flags any element with a role missing required ARIA attributes, including patterns where the native element already provides the state. <input type="checkbox" role="switch"> is flagged for missing aria-checked — a documented false positive.

Fix

Consult axobject-query's elementAXObjects + AXObjectRoles maps to determine when a native element satisfies a given role, mirroring eslint-plugin-jsx-a11y's isSemanticRoleElement. When the pair matches, skip the missing-attribute check for that role.

Adds axobject-query@^4.1.0 as a direct dependency. It's already installed as a transitive dep across the ecosystem; elevates to first-class.

Coverage (non-exhaustive)

Element Role Required ARIA supplied by
<input type="checkbox"> checkbox, switch native checked state
<input type="radio"> radio native checked state
<input type="range"> slider native value / min / max
<input type="number"> spinbutton native value (no required ARIA)
<input type="text"> textbox no required ARIA
<input type="search"> searchbox no required ARIA

Both angle-bracket syntax (<input type="checkbox" role="switch">) and the classic {{input type="checkbox" role="switch"}} helper are handled.

Undocumented pairings still flag

<input type="checkbox" role="menuitemcheckbox"> / <input type="radio" role="menuitemradio"> etc. — axobject-query doesn't list these, so they remain flagged for missing aria-checked. Matches jsx-a11y and angular-eslint behavior.

Why not keep the hand-list?

An earlier revision of this PR hand-maintained a 3-entry {input type}:{role} whitelist. Audit-time review showed:

  • Even the 3 entries had a factual error: the earlier 5-entry version cited axobject-query for menuitemcheckbox/menuitemradio pairings that axobject-query doesn't actually encode. A hand-list encodes the maintainer's (imperfect) understanding of the library's data; using the library directly eliminates the drift class.
  • axobject-query's coverage is strictly broader than what a hand-list would capture (see table above). A hand-list reduces false positives for 3 patterns; axobject-query reduces them for many more.
  • jsx-a11y and @angular-eslint/template already use this approach; aligning removes a documented parity gap.

Prior art

Plugin Rule Mechanism
jsx-a11y role-has-required-aria-props isSemanticRoleElement(type, attributes) via axobject-query's elementAXObjects
@angular-eslint/template role-has-required-aria Same helper, backed by axobject-query
vuejs-accessibility role-has-required-aria-props Hardcoded carve-out for {role: switch, type: checkbox} only — much narrower than axobject-query
lit-a11y role-has-required-aria-attrs No semantic-role exemption at all

This PR aligns with jsx-a11y / angular-eslint on the axobject-query-backed approach. vue-a11y's narrower carve-out and lit-a11y's no-exemption behavior are documented divergences.

What this PR does not fix

Role-token case-insensitivity at the rule level — <input type="checkbox" role="SWITCH"> is silently ignored (aria-query's roles.get(role) is case-sensitive, so the rule short-circuits to null before reaching the isSemanticRoleElement check). That rule-wide gap is addressed in #2728.

Audit fixture

Translated peer-plugin test fixture at tests/audit/role-has-required-aria/peer-parity.js. Not wired into the default test run.

@johanrd johanrd force-pushed the fix/role-required-aria-checkbox-switch branch from 614446f to 5cf4879 Compare April 21, 2026 22:16
@johanrd johanrd changed the title BUGFIX: template-require-mandatory-role-attributes — accept <input type=checkbox> + role=switch fix(template-require-mandatory-role-attributes): use axobject-query for semantic-role exemptions Apr 21, 2026
@johanrd johanrd force-pushed the fix/role-required-aria-checkbox-switch branch 2 times, most recently from 3ff7f86 to cf9f552 Compare April 21, 2026 22:26
…or semantic-role exemptions

Replaces the 3-entry hand-list (`{input type}:{role}` pairings) with a
lookup against axobject-query's `elementAXObjects` + `AXObjectRoles`
maps. Mirrors the approach used by eslint-plugin-jsx-a11y (its
`isSemanticRoleElement` util) and @angular-eslint/template.

## Why

The hand-list covered 3 pairings: `checkbox:checkbox`, `checkbox:switch`,
`radio:radio`. axobject-query encodes substantially more — including
`input[type=range]:slider`, `input[type=number]:spinbutton`,
`input[type=text]:textbox`, `input[type=search]:searchbox`. Each of
these is a case where the native element already provides the role's
required ARIA state (e.g., `<input type=range>` provides value via its
native `value` attribute, satisfying `role=slider`'s `aria-valuenow`
requirement).

Using axobject-query directly:
- Gives us strict superset coverage of the hand-list.
- Stays in sync when axobject-query updates (the hand-list had already
  drifted — the earlier revision incorrectly claimed menuitemcheckbox
  / menuitemradio pairings were in axobject-query when they aren't).
- Matches jsx-a11y / angular-eslint behavior, closing a documented
  parity gap.

Adds `axobject-query@^4.1.0` as a direct dep. It's already a transitive
dep via other ecosystem packages; this elevates it to first-class.

## Changes

- `lib/rules/template-require-mandatory-role-attributes.js` — replace
  `NATIVELY_CHECKED_INPUT_ROLE_PAIRS` + `isNativelyChecked` with
  `isSemanticRoleElement` that walks `elementAXObjects` and checks
  `AXObjectRoles`. Handles both `GlimmerElementNode` (angle-bracket
  syntax) and `GlimmerMustacheStatement` (classic `{{input}}` helper).
- `package.json` — add `axobject-query@^4.1.0`.
- `tests/lib/rules/template-require-mandatory-role-attributes.js` —
  add tests for the broadened coverage (`<input type=range role=slider>`
  now valid in both gts and hbs forms).
- `docs/rules/template-require-mandatory-role-attributes.md` — rewrite
  the exemption section to describe the axobject-query-backed lookup
  with a table of known pairings.
…imary source

axobject-query is the data package the rule queries, but the normative
source for "native <input type=checkbox> exposes aria-checked via the
checked IDL attribute" is HTML-AAM §el-input-checkbox. Adding the HTML-AAM
link makes the exemption's spec grounding explicit instead of leaving
axobject-query as a free-floating data source.
@johanrd johanrd force-pushed the fix/role-required-aria-checkbox-switch branch from f735a42 to 48ef43c Compare April 22, 2026 17:11
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