feat(standards/ariaAttrs): add caseInsensitive property for attributes - #5224
Conversation
There was a problem hiding this comment.
Thanks for tackling this — the caseInsensitive flag is a nice cleanup. A few things to address before merge, mostly around consistency and finishing the replacement.
Important
1. Leftover { lowercase: true } for the now-caseInsensitive attrs. Some call sites weren't updated:
lib/rules/aria-hidden-focus-matches.js:10still passes{ lowercase: true }foraria-hidden(this file isn't in the PR at all).lib/checks/aria/aria-valid-attr-value-evaluate.js:54and:78still pass it foraria-expanded, which is inconsistent witharia-required-attr-evaluate.jswhere the PR removed the same pattern.
These are redundant now and undercut the stated goal of the PR. A repo-wide grep for lowercase: true would confirm none of the four flagged attrs still pass it.
2. Doc/test typo caseInsenstive. doc/standards-object.md:54 and the test name at test/commons/aria/get-aria-value.js:128 spell it caseInsenstive — the actual property is caseInsensitive, so the docs describe a property that doesn't exist.
3. aria-selected is treated case-insensitively but not flagged in the standard. aria-valid-attr-value-evaluate.js:57 lowercases aria-selected via { lowercase: true }, but aria-selected in the standard is not marked caseInsensitive. So axe already treats it case-insensitively at this one call site while the standard says it's case-sensitive. WAI-ARIA treats enumerated token values as case-insensitive generally, so as part of this work we should test the remaining token props to confirm whether they're actually still case-sensitive, and mark them accordingly (or intentionally leave them and document why — see below). At minimum aria-selected should be reconciled so behavior doesn't depend on the call site.
Suggestions
4. Document why some token props are left case-sensitive. Since the spec treats enumerated token values as case-insensitive but this PR flags only four attributes, add a note (in lib/standards/aria-attrs.js and/or the doc) explaining why the remaining token attributes are intentionally left case-sensitive in Axe. Where we don't have test data yet, we'll need to run the tests.
5. Redundant optional chaining. Now that getAriaValue always returns an object, remaining ?.value calls (e.g. aria-valid-attr-value-evaluate.js:45) are dead defensiveness and mix styles within the file. Low priority.
WilcoFiers
left a comment
There was a problem hiding this comment.
I can live with leaving testing of those lesser used props for another time, but in that case please open an issue.
chutchins25
left a comment
There was a problem hiding this comment.
Nice work — 58cb8a08 is a good deal more than was asked for. 20 of the 23 token/boolean attrs now carry an explicit caseInsensitive value, and the per-AT rationale comments on the nine false ones are genuinely useful to have in the standard.
A few things I checked that seem worth recording:
- Every removed
{ lowercase: true }maps to an attribute now markedcaseInsensitive: true— all 8 call sites (aria-invalid,aria-hidden×3,aria-expanded×3,aria-haspopup,aria-selected,aria-live,aria-disabled). No silent loss of lowercasing. - The
getAriaValuereturn-shape change isn't a breaking change:lib/commons/aria/get-aria-value.jsis absent fromv4.11.4,v4.12.0andv4.12.1, andgetAriaValueappears nowhere indoc/. It's never shipped, so there's no public contract to break. - Items 1, 3 and 5 from the first round all check out on this head — zero
lowercase: trueleft inlib/(the three remaining hits are tests of the option itself onaria-label),aria-selectedis now flagged in the standard, and zero?.valueleft in any changed lib file.
One process thing: @WilcoFiers asked for an issue if the lesser-used props were left untested, and I couldn't find one. #4618 is the only open case-sensitivity ticket and it's a broad 2024 "go over the code base" item that doesn't name these attributes, and there's no Closes line on this PR. Worth filing before merge so that condition is actually met.
Plus a question inline about the aria-selected test that lost its uppercase case, and a note on aria-attrs.js about the three unresolved attrs.
| @@ -357,7 +357,6 @@ describe('text.formControlValue', () => { | |||
| const target = queryFixture(html` | |||
| <div id="target" role="listbox"> | |||
| <div role="option" aria-selected="false">foo</div> | |||
There was a problem hiding this comment.
Dropping the aria-selected="TRUE" case from this test makes sense — with aria-selected now caseInsensitive: true it resolves to 'true', so "bar" becomes selected and this assertion would flip from '' to 'bar'.
The part I'd flag is that nothing replaced it. As far as I can tell it was the only test touching uppercase aria-selected, so the change to ariaListboxValue (form-control-value.js:150-154) now has no coverage at this call site. Your note that you'd tested it is on the thread attached to the deleted line, so the verification exists but isn't captured in code. Worth adding the positive counterpart?
it('returns the option when aria-selected is not lowercase', () => {
const target = queryFixture(html`
<div id="target" role="listbox">
<div role="option" aria-selected="false">foo</div>
<div role="option" aria-selected="TRUE">bar</div>
</div>
`);
assert.equal(ariaListboxValue(target), 'bar');
});(Plain block rather than a suggestion since it needs to land after this test's closing brace.)
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 🤖 🎉
Add the
caseInsensitiveproperty to the aria attrs standard. This is used to automatically lowercase the value forcommons.aria.getAriaValueinstead of having to manually pass{ lowercase: true }every time you wanted to access one of the attributes. This updatesaria-expanded,aria-hidden,aria-haspopup,aria-selected,aria-invalid,aria-disabled, andara-liveto be case insensitive (which we have tested in prior prs).Additionally, as a
chorestyle change this also updatesgetAriaValueto not returnnullanymore but instead{ value: null, source: null }so that we don't have to conditionally access thevalueproperty of the return.