From 28526f4faad4a3000d5595c8e3019ace4bf49651 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 19 Feb 2026 19:08:09 -0500 Subject: [PATCH] Extract rule: template-no-aria-hidden-body --- README.md | 1 + docs/rules/template-no-aria-hidden-body.md | 31 ++++++++++++ lib/rules/template-no-aria-hidden-body.js | 49 +++++++++++++++++++ .../lib/rules/template-no-aria-hidden-body.js | 21 ++++++++ 4 files changed, 102 insertions(+) create mode 100644 docs/rules/template-no-aria-hidden-body.md create mode 100644 lib/rules/template-no-aria-hidden-body.js create mode 100644 tests/lib/rules/template-no-aria-hidden-body.js diff --git a/README.md b/README.md index 330bed0756..da256ad269 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ rules in templates can be disabled with eslint directives with mustache or html | [template-link-href-attributes](docs/rules/template-link-href-attributes.md) | require href attribute on link elements | | | | | [template-no-abstract-roles](docs/rules/template-no-abstract-roles.md) | disallow abstract ARIA roles | | | | | [template-no-accesskey-attribute](docs/rules/template-no-accesskey-attribute.md) | disallow accesskey attribute | | 🔧 | | +| [template-no-aria-hidden-body](docs/rules/template-no-aria-hidden-body.md) | disallow aria-hidden on body element | | 🔧 | | | [template-no-aria-unsupported-elements](docs/rules/template-no-aria-unsupported-elements.md) | disallow ARIA roles, states, and properties on elements that do not support them | | | | ### Best Practices diff --git a/docs/rules/template-no-aria-hidden-body.md b/docs/rules/template-no-aria-hidden-body.md new file mode 100644 index 0000000000..8a83af5818 --- /dev/null +++ b/docs/rules/template-no-aria-hidden-body.md @@ -0,0 +1,31 @@ +# ember/template-no-aria-hidden-body + +🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + +The aria-hidden attribute should never be present on the `` element, as it hides the entire document from assistive technology. + +## Examples + +This rule **forbids** the following: + +```hbs + +``` + +```hbs + +``` + +This rule **allows** the following: + +```hbs + +``` + +## References + +- [Using the aria-hidden attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-hidden_attribute) +- [How Lighthouse identifies hidden body elements](https://web.dev/aria-hidden-body/) +- [WCAG 4.1.2 - Name, Role, Value (Level A)](https://www.w3.org/TR/WCAG21/#name-role-value) diff --git a/lib/rules/template-no-aria-hidden-body.js b/lib/rules/template-no-aria-hidden-body.js new file mode 100644 index 0000000000..4c7aec0257 --- /dev/null +++ b/lib/rules/template-no-aria-hidden-body.js @@ -0,0 +1,49 @@ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow aria-hidden on body element', + category: 'Accessibility', + strictGjs: true, + strictGts: true, + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-aria-hidden-body.md', + }, + fixable: 'code', + schema: [], + messages: { + noAriaHiddenBody: + 'The aria-hidden attribute should never be present on the element, as it hides the entire document from assistive technology', + }, + }, + + create(context) { + return { + GlimmerElementNode(node) { + if (node.tag === 'body') { + const ariaHiddenAttr = node.attributes?.find((attr) => attr.name === 'aria-hidden'); + + if (ariaHiddenAttr) { + context.report({ + node: ariaHiddenAttr, + messageId: 'noAriaHiddenBody', + fix(fixer) { + const sourceCode = context.sourceCode; + const text = sourceCode.getText(); + const attrStart = ariaHiddenAttr.range[0]; + const attrEnd = ariaHiddenAttr.range[1]; + + let removeStart = attrStart; + while (removeStart > 0 && /\s/.test(text[removeStart - 1])) { + removeStart--; + } + + return fixer.removeRange([removeStart, attrEnd]); + }, + }); + } + } + }, + }; + }, +}; diff --git a/tests/lib/rules/template-no-aria-hidden-body.js b/tests/lib/rules/template-no-aria-hidden-body.js new file mode 100644 index 0000000000..26861b7c3f --- /dev/null +++ b/tests/lib/rules/template-no-aria-hidden-body.js @@ -0,0 +1,21 @@ +const rule = require('../../../lib/rules/template-no-aria-hidden-body'); +const RuleTester = require('eslint').RuleTester; + +const ruleTester = new RuleTester({ + parser: require.resolve('ember-eslint-parser'), + parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, +}); + +ruleTester.run('template-no-aria-hidden-body', rule, { + valid: [ + '', + '', + ], + invalid: [ + { + code: '', + output: '', + errors: [{ messageId: 'noAriaHiddenBody' }], + }, + ], +});