Skip to content

Commit 7f76868

Browse files
add docs and htmlPropName
1 parent 6b767a5 commit 7f76868

File tree

2 files changed

+33
-13
lines changed

2 files changed

+33
-13
lines changed

docs/rules/a11y-no-visually-hidden-interactive-element.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@
66

77
This rule guards against visually hiding interactive elements. If a sighted keyboard user navigates to an interactive element that is visually hidden they might become confused and assume that keyboard focus has been lost.
88

9+
Note: we are not guarding against visually hidden `input` elements at this time. Some visually hidden inputs might cause a false positive (e.g. some file inputs).
10+
11+
### Why do we visually hide content?
12+
13+
Visually hiding content can be useful when you want to provide information specifically to screen reader users or other assitive technology users while keeping content hidden from sighted users.
14+
15+
Applying the following css will visually hide content while still making it accessible to screen reader users.
16+
17+
```css
18+
clip-path: inset(50%);
19+
height: 1px;
20+
overflow: hidden;
21+
position: absolute;
22+
white-space: nowrap;
23+
width: 1px;
24+
```
25+
926
👎 Examples of **incorrect** code for this rule:
1027

1128
```jsx
@@ -40,13 +57,18 @@ This rule guards against visually hiding interactive elements. If a sighted keyb
4057

4158
## Options
4259

60+
- className - A css className that visually hides content. Defaults to `sr-only`.
61+
- componentName - A react component name that visually hides content. Defaults to `VisuallyHidden`.
62+
- htmlPropName - A prop name used to replace the semantic element that is rendered. Defaults to `as`.
63+
4364
```json
4465
{
4566
"a11y-no-visually-hidden-interactive-element": [
4667
"error",
4768
{
4869
"className": "visually-hidden",
49-
"componentName": "VisuallyHidden"
70+
"componentName": "VisuallyHidden",
71+
"htmlPropName": "as"
5072
}
5173
]
5274
}

lib/rules/a11y-no-visually-hidden-interactive-element.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ const {generateObjSchema} = require('eslint-plugin-jsx-a11y/lib/util/schemas')
44

55
const defaultClassName = 'sr-only'
66
const defaultcomponentName = 'VisuallyHidden'
7+
const defaultHtmlPropName = 'as'
78

89
const schema = generateObjSchema({
910
className: {type: 'string'},
1011
componentName: {type: 'string'},
12+
htmlPropName: {type: 'string'},
1113
})
1214

1315
/** Note: we are not including input elements at this time
@@ -16,9 +18,9 @@ const schema = generateObjSchema({
1618
*/
1719
const INTERACTIVELEMENTS = ['a', 'button', 'summary', 'select', 'option', 'textarea']
1820

19-
const checkIfInteractiveElement = (context, node) => {
21+
const checkIfInteractiveElement = (context, htmlPropName, node) => {
2022
const elementType = getElementType(context, node.openingElement)
21-
const asProp = getPropValue(getProp(node.openingElement.attributes, 'as'))
23+
const asProp = getPropValue(getProp(node.openingElement.attributes, htmlPropName))
2224

2325
for (const interactiveElement of INTERACTIVELEMENTS) {
2426
if ((asProp ?? elementType) === interactiveElement) {
@@ -29,29 +31,24 @@ const checkIfInteractiveElement = (context, node) => {
2931
}
3032

3133
// if the node is visually hidden recursively check if it has interactive children
32-
const checkIfVisuallyHiddenAndInteractive = (context, className, componentName, node, isParentVisuallyHidden) => {
34+
const checkIfVisuallyHiddenAndInteractive = (context, options, node, isParentVisuallyHidden) => {
35+
const {className, componentName, htmlPropName} = options
3336
if (node.type === 'JSXElement') {
3437
const classes = getPropValue(getProp(node.openingElement.attributes, 'className'))
3538
const isVisuallyHiddenElement = node.openingElement.name.name === componentName
3639
const hasSROnlyClass = typeof classes !== 'undefined' && classes.includes(className)
3740
let isHidden = false
3841

3942
if (hasSROnlyClass || isVisuallyHiddenElement || !!isParentVisuallyHidden) {
40-
if (checkIfInteractiveElement(context, node)) {
43+
if (checkIfInteractiveElement(context, htmlPropName, node)) {
4144
return true
4245
}
4346
isHidden = true
4447
}
4548
if (node.children && node.children.length > 0) {
4649
return (
4750
typeof node.children?.find(child =>
48-
checkIfVisuallyHiddenAndInteractive(
49-
context,
50-
className,
51-
componentName,
52-
child,
53-
!!isParentVisuallyHidden || isHidden,
54-
),
51+
checkIfVisuallyHiddenAndInteractive(context, options, child, !!isParentVisuallyHidden || isHidden),
5552
) !== 'undefined'
5653
)
5754
}
@@ -73,10 +70,11 @@ module.exports = {
7370
const config = options[0] || {}
7471
const className = config.className || defaultClassName
7572
const componentName = config.componentName || defaultcomponentName
73+
const htmlPropName = config.htmlPropName || defaultHtmlPropName
7674

7775
return {
7876
JSXElement: node => {
79-
if (checkIfVisuallyHiddenAndInteractive(context, className, componentName, node, false)) {
77+
if (checkIfVisuallyHiddenAndInteractive(context, {className, componentName, htmlPropName}, node, false)) {
8078
context.report({
8179
node,
8280
message:

0 commit comments

Comments
 (0)