Skip to content

fix: template-no-invalid-aria-attributes — skip custom elements (hyphenated tags)#2732

Closed
johanrd wants to merge 4 commits intoember-cli:masterfrom
johanrd:fix/aria-props-skip-custom-elements
Closed

fix: template-no-invalid-aria-attributes — skip custom elements (hyphenated tags)#2732
johanrd wants to merge 4 commits intoember-cli:masterfrom
johanrd:fix/aria-props-skip-custom-elements

Conversation

@johanrd
Copy link
Copy Markdown
Contributor

@johanrd johanrd commented Apr 21, 2026

  • Premise: HTML custom elements (tag names containing a hyphen, per WHATWG valid-custom-element-name) may define their own accessibility contract — typically via ElementInternals — and can legitimately carry aria-* attributes whose validity depends on the element's internally-set default role. ESLint cannot introspect that role.
  • Problem: our rule flagged aria-* on every tag, including custom elements, producing false positives for patterns that work correctly at runtime.

Fix: skip the check when node.tag is a custom element (lowercase + hyphen heuristic).

Heuristic vs. full spec

The WHATWG valid-custom-element-name grammar has more nuance than "lowercase + hyphen":

  • The tag must start with an ASCII lower alpha and must not contain ASCII upper alpha.
  • The reserved names annotation-xml, color-profile, font-face, font-face-src, font-face-uri, font-face-format, font-face-name, and missing-glyph are NOT valid custom element names despite containing hyphens.

This PR's check is lowercase + includes('-'), which over-accepts the reserved names. In practice these SVG/MathML legacy names rarely appear with aria-* in Ember templates, so the over-acceptance is acceptable. Worth a docstring note in the rule.

Peer plugin behavior — verified in source

The prior claim "Matches angular-eslint's valid-aria behavior" was imprecise:

Plugin File Behavior
@angular-eslint/template valid-aria.ts:48-54 Uses an allowlist from getDomElements() (aria-query's dom keys). Custom elements are excluded because they're not in the allowlist, not via a hyphen heuristic. Same outcome for hyphenated tags; diverges on non-hyphenated unknown tags (e.g. <foo> — angular skips, our rule would still check).
jsx-a11y aria-props.js:39-57 Validates every aria-* attribute on every element. No custom-element skip. (Reasonable — JSX doesn't directly model hyphenated tags.)
vuejs-accessibility aria-props.ts Validates every aria-prefixed attribute. No custom-element skip.
lit-a11y aria-attrs.js:37-60 Validates every attribute on every element via isInvalidAriaAttribute. An isCustomElement.js helper exists in the repo but is not used by aria-attrs. No custom-element skip. (Notable given lit-a11y targets Lit components, which ARE custom elements.)

So the custom-element skip aligns us with angular-eslint in outcome for the hyphenated-tag case, but the three other peers all validate custom elements. We're the second plugin (after angular-eslint) to carve them out.

The "shadow-DOM-mapped role" phrasing in the original description is imprecise — the actual mechanism for author-defined roles on custom elements is ElementInternals. Updated here.

Audit fixture

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

johanrd and others added 3 commits April 21, 2026 12:53
…enated tags)

HTML custom elements (tags with a hyphen, per WHATWG valid-custom-element-name)
define their own a11y contracts; they may intentionally use aria-* attributes
whose validity depends on the component's shadow-DOM-mapped role, which ESLint
cannot introspect. Our rule previously flagged aria-* on every tag, producing
false positives for these patterns.

Skip the check when node.tag is a custom element (lowercase + hyphen). Matches
angular-eslint's valid-aria behavior.

Ref: audit tracking PR #28 item G6.
Translates 94 cases from peer-plugin rules:
  - jsx-a11y aria-props
  - vuejs-accessibility aria-props
  - lit-a11y aria-unsupported-elements / valid-aria-*
  - @angular-eslint/template valid-aria

Fixture documents parity after this fix: custom elements (hyphenated
tags) now skip the check. jsx-a11y has no equivalent concept and
would still flag `<app-custom aria-x="text">`, which is documented
inline.
@johanrd
Copy link
Copy Markdown
Contributor Author

johanrd commented Apr 21, 2026

Closing after deeper audit.

Why

The premise — "custom elements have author-defined a11y contracts; ESLint can't introspect, so skip" — doesn't hold up against peer-plugin evidence:

Plugin Custom-element handling
jsx-a11y (aria-props) Validates every aria-* attribute on every element. No skip.
vuejs-accessibility (aria-props) Same. No skip.
lit-a11y (aria-attrs) Same. No skip — notable, because lit-a11y specifically targets Lit components (which ARE custom elements).
@angular-eslint/template (valid-aria) Skips custom elements via an allowlist of known DOM tags (aria-query's dom keys). Diverges on non-hyphenated unknown tags too.

Only one peer skips, and it uses a different mechanism (allowlist, not a hyphen heuristic). The other three — including the plugin specifically designed for web components — validate custom elements normally.

In practice, aria-* attributes are a standardized namespace. There's no legitimate reason a custom element would use non-standard aria-* names or off-spec values. The "false positive" concern that motivated the PR is theoretical, not observed.

If a real false positive emerges

The right response would be a config option like customElementTags: ['my-widget', ...] for explicit opt-out on specific known custom elements. That's a follow-up if/when someone reports an actual false positive — not worth building speculatively.

Closing this in favor of matching the majority peer behavior (validate all elements). No changes to master.

@johanrd johanrd closed this Apr 21, 2026
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