Skip to content

feat(aria-allowed/prohibited-attr, aria-required-parent/children): partially support element internals role#5080

Open
straker wants to merge 4 commits intodevelopfrom
elm-internals-implicit-role
Open

feat(aria-allowed/prohibited-attr, aria-required-parent/children): partially support element internals role#5080
straker wants to merge 4 commits intodevelopfrom
elm-internals-implicit-role

Conversation

@straker
Copy link
Copy Markdown
Contributor

@straker straker commented Apr 22, 2026

Note that this can only support rules that don't select for [role] on the element itself, which is why we can't support aria-required-attr yet, or any rules that look for specific roles (e.g. aria-command-name). Additionally we can only test the supported rules partially as those rules look for an element with [role]. For example, for aria-required-parent we can test that the parent of an element with [role] has a required internal role, but we cannot test a child with an internal role has a required parent.

Fully supporting rules can only be supported once we've figured out how to update our selectors to look at more than just CSS selectors (planned but not fully spec'd out yet).

Closes: #5039
Closes: #4259

@straker straker requested a review from a team as a code owner April 22, 2026 22:41
Comment thread test/testutils.js
fixture.setAttribute('id', 'fixture');
document.body.insertBefore(fixture, document.body.firstChild);
}
testUtils.fixture = fixture;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Adding fixture to the testutils objects makes getting it a bit easier instead of having to do const fixture = document.querySelector('#fixture') in every test that needs it.

afterEach(function () {
fixture.innerHTML = '';
});
describe('aria.implicitRole', () => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Most of this file is cleaning it up to match the new es6 style. Bottom of the file has the newest tests.

@@ -1,170 +1,115 @@
describe('aria-required-parent', function () {
'use strict';
describe('aria-required-parent', () => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Most of this file is cleaning it up to match the new es6 style. Bottom of the file has the newest tests.

} else if (role || hasGlobalAriaOrFocusable) {
const attr = globalAriaAttr || 'tabindex';
const attr =
globalAriaAttr || (vNode.hasAttr('tabindex') ? 'tabindex' : undefined);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This code assumed that if an element had a role it also had a global aria attr available, which wasn't true. This lead to a custom element with an internal role to always have [tabindex] added to the selector even though that attr didn't exist.

@straker straker changed the title feat(aria-allowed/prohivited-attr, aria-required-parent/children): partially support element internals role feat(aria-allowed/prohibited-attr, aria-required-parent/children): partially support element internals role Apr 22, 2026
describe('ElementInternals', () => {
it('should detect incorrectly used attributes', () => {
const vNode = queryFixture(
'<testutils-element with-role="link" id="target" tabindex="1" aria-selected="true"></testutils-element>'
Copy link
Copy Markdown
Contributor Author

@straker straker Apr 22, 2026

Choose a reason for hiding this comment

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

See the changes to the test/testutils.js file for how this test custom element works.

Copy link
Copy Markdown
Contributor

@chutchins25 chutchins25 left a comment

Choose a reason for hiding this comment

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

Nice work! A few questions for you:

  1. Are we ok bundling large refactors like this in with the feature work? Would it be worth splitting the ES6 cleanup into its own PR next time to keep the feature diff focused? CLAUDE.md §3 leans that way, but curious if there's a reason the bundle made sense here.

  2. On naming — since internals lands on VirtualNode as public-ish surface (consumers can reach it through any VirtualNode instance), renaming it later would be a breaking change. Was elementInternals considered as a more explicit name, or is the short form intentional?

Comment thread lib/core/utils/get-element-internals.js
Comment thread test/checks/aria/required-parent.js
Comment thread test/core/utils/get-element-internals.js Outdated
Co-authored-by: Chris Hutchins <chris.hutchins@deque.com>
@straker
Copy link
Copy Markdown
Contributor Author

straker commented Apr 23, 2026

  1. Are we ok bundling large refactors like this in with the feature work? Would it be worth splitting the ES6 cleanup into its own PR next time to keep the feature diff focused? CLAUDE.md §3 leans that way, but curious if there's a reason the bundle made sense here.

Yep. Any time we edit files that use the old syntax we convert the file first to the new syntax and then add the changes. It allows us to slowly convert the files over time without having to take the time to do it on it's own.

  1. On naming — since internals lands on VirtualNode as public-ish surface (consumers can reach it through any VirtualNode instance), renaming it later would be a breaking change. Was elementInternals considered as a more explicit name, or is the short form intentional?

I can rename it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants