From 82e765211fa26175c6ac08d0b94763c72f399587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20R=C3=B8ed?= Date: Tue, 21 Apr 2026 07:21:49 +0200 Subject: [PATCH] fix(template-require-aria-activedescendant-tabindex): accept tabindex="-1" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before: any tabindex below 0 was flagged, and the autofix replaced tabindex="-1" with tabindex="0". That silently changed semantics — -1 is the canonical "focusable but not in tab order" value that composite widgets with aria-activedescendant specifically want. tabindex semantics: "0" — focusable, in the natural tab order "-1" — focusable programmatically (e.g. via roving focus), skipped in tab order Both are valid for elements that manage focus via aria-activedescendant; see the W3C APG entry on "Managing focus in composites using aria-activedescendant": https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant Matches the check upstream in eslint-plugin-jsx-a11y (aria-activedescendant-has-tabindex.js: `if (tabIndex >= -1) return;`). lit-a11y's aria-activedescendant-has-tabindex has the same semantics. Rule doc updated to describe the new accepted range. Tests moved the three tabindex="-1" cases from invalid to valid. --- ...ate-require-aria-activedescendant-tabindex.md | 10 ++++++---- ...ate-require-aria-activedescendant-tabindex.js | 2 +- ...ate-require-aria-activedescendant-tabindex.js | 16 ++++++---------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/rules/template-require-aria-activedescendant-tabindex.md b/docs/rules/template-require-aria-activedescendant-tabindex.md index 4a4be8d807..155e50ce8d 100644 --- a/docs/rules/template-require-aria-activedescendant-tabindex.md +++ b/docs/rules/template-require-aria-activedescendant-tabindex.md @@ -6,11 +6,11 @@ -This rule requires all non-interactive HTML elements using the `aria-activedescendant` attribute to declare a `tabindex` of zero. +This rule requires non-interactive HTML elements using the `aria-activedescendant` attribute to declare a `tabindex` of `0` or `-1`. The `aria-activedescendant` attribute identifies the active descendant element of a composite widget, textbox, group, or application with document focus. This attribute is placed on the container element of the input control, and its value is set to the ID of the active child element. This allows screen readers to communicate information about the currently active element as if it has focus, while actual focus of the DOM remains on the container element. -Elements with `aria-activedescendant` must have a `tabindex` of zero in order to support keyboard navigation. Besides interactive elements, which are inherently keyboard-focusable, elements using the `aria-activedescendant` attribute must declare a `tabIndex` of zero with the `tabIndex` attribute. +Elements with `aria-activedescendant` must be focusable to support keyboard navigation. `tabindex="0"` puts the element in the natural tab order; `tabindex="-1"` makes it focusable programmatically (e.g. via roving focus) but skips it in the tab order. Both are valid patterns for composite widgets — see the [W3C APG — Managing focus in composites using aria-activedescendant](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant). ## Examples @@ -19,8 +19,8 @@ This rule **forbids** the following: ```gjs ``` @@ -32,9 +32,11 @@ This rule **allows** the following:
+
+ ``` diff --git a/lib/rules/template-require-aria-activedescendant-tabindex.js b/lib/rules/template-require-aria-activedescendant-tabindex.js index 7bed6e89aa..27e1cdf897 100644 --- a/lib/rules/template-require-aria-activedescendant-tabindex.js +++ b/lib/rules/template-require-aria-activedescendant-tabindex.js @@ -91,7 +91,7 @@ module.exports = { const tabindexValue = getTabindexNumericValue(tabindexAttr); - if (!Number.isFinite(tabindexValue) || tabindexValue < 0) { + if (!Number.isFinite(tabindexValue) || tabindexValue < -1) { context.report({ node, messageId: 'missingTabindex', diff --git a/tests/lib/rules/template-require-aria-activedescendant-tabindex.js b/tests/lib/rules/template-require-aria-activedescendant-tabindex.js index cbacfb2e53..c430611934 100644 --- a/tests/lib/rules/template-require-aria-activedescendant-tabindex.js +++ b/tests/lib/rules/template-require-aria-activedescendant-tabindex.js @@ -13,6 +13,12 @@ const validHbs = [ '', '', '', + // tabindex="-1" is focusable-but-not-tabbable — the canonical pattern for + // composite widgets that manage focus via roving focus / aria-activedescendant. + // See W3C APG — https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant + '
', + '
', + '', ]; const invalidHbs = [ @@ -26,11 +32,6 @@ const invalidHbs = [ output: '
', errors: [{ message: ERROR_MESSAGE }], }, - { - code: '
', - output: '
', - errors: [{ message: ERROR_MESSAGE }], - }, { code: '
', output: '
', @@ -41,11 +42,6 @@ const invalidHbs = [ output: '', errors: [{ message: ERROR_MESSAGE }], }, - { - code: '', - output: '', - errors: [{ message: ERROR_MESSAGE }], - }, ]; function wrapTemplate(entry) {