Skip to content

feat(commons/aria): support ARIA element internals properties#5171

Open
straker wants to merge 8 commits into
developfrom
internals-commons-aria
Open

feat(commons/aria): support ARIA element internals properties#5171
straker wants to merge 8 commits into
developfrom
internals-commons-aria

Conversation

@straker

@straker straker commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Updates the commons/aria directory to use the new getAriaValue, hasAriaValue, and getResolvedRefs functions where it makes sense.

Here's a report of the full directory:

  • commons/aria/arialabel-text - aria-label → getAriaValue
  • commons/aria/arialabelledby-text - aria-labelledby: .attr() check → hasAriaValue; idrefs() → getResolvedRefs; since getResolvedRefs returns VirtualNodes, switched accessibleText(elm) to accessibleTextVirtual(elm) for the direct VirtualNode
  • commons/aria/get-owned-virtual - aria-owns: .hasAttr() → hasAriaValue; idrefs(actualNode, ...) + getNodeFromTree() → getResolvedRefs (already returns VirtualNodes, so getNodeFromTree is no longer needed)
  • commons/aria/label-virtual - aria-labelledby: .attr() check → hasAriaValue; idrefs() + getNodeFromTree() → getResolvedRefs; aria-label → getAriaValue
  • commons/aria/validate-attr-value - validates attrs, no conversion at this time
  • commons/aria/get-accessible-refs - uses node.hasAttribute('for') on raw DOM node for HTML for attribute, not an ARIA prop
  • commons/aria/get-aria-value - this is the new function being adopted
  • commons/aria/get-element-unallowed-roles - checks role attribute, not an ARIA prop
  • commons/aria/get-role - matching browser support and not using internals global attrs to check for role resolution, and also not running implicit role for role resolution if it's a custom element
  • commons/aria/has-aria-value - this is the new function being adopted
  • commons/aria/lookup-table - deprecated
  • commons/aria/allowed-attr / get-explicit-role / get-role-type / get-roles-by-type / get-roles-with-name-from-contents / implicit-role / implicit-nodes / is-accessible-ref / is-aria-role-allowed-on-element / is-combobox-popup / is-unsupported-role / is-valid-role / label / named-from-contents / required-attr / required-context / required-owned / validate-attr - no ARIA attr value access

Closes: #5140

@straker straker requested a review from a team as a code owner June 15, 2026 21:58
chutchins25
chutchins25 previously approved these changes Jun 17, 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.

Looks good to me.

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

We need to look at presentational inheritance.

Comment thread lib/commons/aria/arialabel-text.js
flatTreeSetup(fixture);
const node = fixture.querySelector('#target');
assert.equal(aria.getRole(node), 'presentation');
});

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.

There's another interesting scenario here we didn't talk about:

Suggested change
});
it('handles presentation role inheritance on an ElementInternal role', () => {
fixture.innerHTML =
'<ul role="none"><testutils-element with-role="listitem" id="target"></testutils-element></ul>';
flatTreeSetup(fixture);
const node = fixture.querySelector('#target');
assert.equal(aria.getRole(node), 'presentation');
});
it('does not set presentation role when ElementInternals isn\'t an allowed child', () => {
fixture.innerHTML =
'<ul role="none"><testutils-element with-role="button" id="target"></testutils-element></ul>';
flatTreeSetup(fixture);
const node = fixture.querySelector('#target');
assert.equal(aria.getRole(node), 'button');
});

I'm not actually sure this is how it works in practice. But role=none on ul SHOULD set its child listitems to presentational as well. We should test this I think, and maybe raise a case for in WPT if they don't already have one.

@chutchins25 chutchins25 Jun 18, 2026

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.

Tried to verify the actual behavior:
getRole returns listitem (and button stays button).
Presentational inheritance is keyed off the HTML tag name (li, td, …) in inheritsPresentationChain, not the computed role, so a testutils-element never matches.

Added two tests documenting this current behavior. Re-keying inheritance off computed role is a broader change (also affects <div role="listitem"> etc.) and a genuine spec/WPT question, so I've split it into a follow-up: #5181.

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.

Verified actual browser behavior for this exact case. Both Chrome and Firefox leave the custom element as listitem — neither applies presentational inheritance to an internal role (Chrome strips a native <li> to none, Firefox to generic, but both keep the custom element as listitem). So the current tests document the behavior that matches browsers.

This is the same spec-ahead-of-browsers gap as #5162 (internal-role conflict resolution — Chrome/Firefox/Safari/WPT bugs already filed there), so I've folded the follow-up into #5162 and closed #5181 rather than tracking it separately. Let me know if you're good with matching browsers here.

@pkra pkra Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@WilcoFiers

But role=none on ul SHOULD set its child listitems to presentational as well.

I think ARIA only requires inheritance for (host-language-)required child elements that do not have explicit roles. (Though I spotted some odd phrasing that seems to have been introduced accidentally - I've filed w3c/aria#2832 to clarify.)

(Then again ul > test-element is invalid HTML and its repair is not specified anywhere, I think.)

@chutchins25 chutchins25 self-assigned this Jun 18, 2026
Custom elements exposing a required-child role via ElementInternals do not
inherit a presentational role from an ancestor role=none list, since
inheritance is keyed off the HTML tag name. See #5181.
… presentation

Both Chrome and Firefox leave a custom element with an internal listitem
role as listitem under role="none" (verified), matching the documented
behavior. Re-point the note from #5181 to #5162, which tracks internal-role
conflict resolution and the filed browser/WPT bugs.
Add consumer-level tests for the reflected ariaLabelledByElements property
(precedence over aria-labelledby and aria-label) in arialabelledbyText and
labelVirtual. getResolvedRefs already honors the property; this regression-
tests the #4943 scenario. Tests adapted from #5187.

Co-authored-by: JC Franco <jfranco@esri.com>
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.

ElementInternals: convert lib/commons/aria to getAriaValue/hasAriaValue

4 participants