forked from jsx-eslint/eslint-plugin-jsx-a11y
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[New] add
no-aria-hidden-on-focusable
rule
Raise error when aria-hidden="true" is set on focusable element. Exceptions are if `tabindex=-1`. Fixes jsx-eslint#881
- Loading branch information
Showing
3 changed files
with
121 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/** | ||
* @fileoverview Enforce `aria-hidden="true"` is not used on focusable elements. | ||
* @author Kate Higa | ||
*/ | ||
|
||
// ----------------------------------------------------------------------------- | ||
// Requirements | ||
// ----------------------------------------------------------------------------- | ||
|
||
import { RuleTester } from 'eslint'; | ||
import parserOptionsMapper from '../../__util__/parserOptionsMapper'; | ||
import rule from '../../../src/rules/no-aria-hidden-on-focusable'; | ||
// ----------------------------------------------------------------------------- | ||
// Tests | ||
// ----------------------------------------------------------------------------- | ||
|
||
const ruleTester = new RuleTester(); | ||
|
||
const expectedError = { | ||
message: 'aria-hidden="true" must not be set on focusable elements.', | ||
type: 'JSXOpeningElement', | ||
}; | ||
|
||
ruleTester.run('no-aria-hidden-on-focusable', rule, { | ||
valid: [ | ||
{ code: '<div aria-hidden="true" />;' }, | ||
{ code: '<div onClick={() => void 0} aria-hidden="true" />;' }, | ||
{ code: '<img aria-hidden="true" />' }, | ||
{ code: '<a aria-hidden="false" href="#" />' }, | ||
{ code: '<button aria-hidden="true" tabIndex="-1" />' }, | ||
{ code: '<button />' }, | ||
{ code: '<a href="/" />' }, | ||
].map(parserOptionsMapper), | ||
invalid: [ | ||
{ code: '<div aria-hidden="true" tabIndex="0" />;', errors: [expectedError] }, | ||
{ code: '<input aria-hidden="true" />;', errors: [expectedError] }, | ||
{ code: '<a href="/" aria-hidden="true" />', errors: [expectedError] }, | ||
{ code: '<button aria-hidden="true" />', errors: [expectedError] }, | ||
{ code: '<textarea aria-hidden="true" />', errors: [expectedError] }, | ||
{ code: '<p tabindex="0" aria-hidden="true">text</p>;', errors: [expectedError] }, | ||
].map(parserOptionsMapper), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# no-aria-hidden-on-focusable | ||
|
||
Enforce that `aria-hidden="true"` is not set on focusable elements. | ||
|
||
`aria-hidden="true"` can be used to hide purely decorative content from screen reader users. An element with `aria-hidden="true"` that can also be reached by keyboard can lead to confusion or unexpected behavior for screen reader users. Avoid using `aria-hidden="true"` on focusable elements. | ||
|
||
## Rule details | ||
|
||
### Succeed | ||
```jsx | ||
<div aria-hidden="true" /> | ||
<img aria-hidden="true" /> | ||
<a aria-hidden="false" href="#" /> | ||
<button aria-hidden="true" tabIndex="-1" /> // `tabIndex=-1` removes the element from sequential focus navigation so we don't flag it. | ||
<a href="/" /> | ||
<div aria-hidden="true"><a href="#"></a></div> // This is also bad but will not be handled by this rule. | ||
``` | ||
|
||
### Fail | ||
```jsx | ||
<div aria-hidden="true" tabIndex="0" /> | ||
<input aria-hidden="true" /> | ||
<a href="/" aria-hidden="true" /> | ||
<button aria-hidden="true" /> | ||
<textarea aria-hidden="true" /> | ||
``` | ||
|
||
## Accessibility guidelines | ||
General best practice (reference resources) | ||
|
||
### Resources | ||
|
||
- [aria-hidden elements do not contain focusable elements](https://dequeuniversity.com/rules/axe/html/4.4/aria-hidden-focus) | ||
- [Element with aria-hidden has no content in sequential focus navigation](https://www.w3.org/WAI/standards-guidelines/act/rules/6cfa84/proposed/) | ||
- [MDN aria-hidden](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/** | ||
* @fileoverview Enforce aria-hidden is not used on interactive elements or contain interactive elements. | ||
* @author Kate Higa | ||
*/ | ||
|
||
// ---------------------------------------------------------------------------- | ||
// Rule Definition | ||
// ---------------------------------------------------------------------------- | ||
|
||
import { getProp, getPropValue } from 'jsx-ast-utils'; | ||
import getElementType from '../util/getElementType'; | ||
import isFocusable from '../util/isFocusable'; | ||
import { generateObjSchema } from '../util/schemas'; | ||
|
||
const errorMessage = 'aria-hidden="true" must not be set on focusable elements.'; | ||
const schema = generateObjSchema(); | ||
|
||
export default { | ||
meta: { | ||
docs: { | ||
url: 'https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/no-aria-hidden-on-focusable.md', | ||
description: errorMessage, | ||
}, | ||
schema: [schema], | ||
}, | ||
|
||
create(context) { | ||
const elementType = getElementType(context); | ||
return { | ||
JSXOpeningElement(node) { | ||
const { attributes } = node; | ||
const type = elementType(node); | ||
const isAriaHidden = getPropValue(getProp(attributes, 'aria-hidden')) === true; | ||
|
||
if (isAriaHidden && isFocusable(type, attributes)) { | ||
context.report({ | ||
node, | ||
message: errorMessage, | ||
}); | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |