Skip to content

[New rule] disallow aria-hidden="true" on focusable elements#888

Merged
ljharb merged 2 commits intojsx-eslint:mainfrom
khiga8:kh-disallow-aria-hidden-on-interactive
Aug 31, 2022
Merged

[New rule] disallow aria-hidden="true" on focusable elements#888
ljharb merged 2 commits intojsx-eslint:mainfrom
khiga8:kh-disallow-aria-hidden-on-interactive

Conversation

@khiga8
Copy link
Copy Markdown
Contributor

@khiga8 khiga8 commented Aug 26, 2022

Fixes: #881

This adds a new rule that flags when aria-hidden="true" is set on focusable elements or contain child elements that are focusable. People often set aria-hidden="true" to hide an element from AT users, but if the element is focusable, AT users can still access it which can be confusing.

I wasn't sure what the contributing process is like so wanted to get a draft PR going to get thoughts. I looked through the list of jsx-a11y rules and I couldn't find a rule for something like this but let me know if I missed it. If maintainers are okay moving forward with this, I will add a doc page. I've ran the lint and added a test.

Related references:

@khiga8 khiga8 changed the title [New] disallow aria-hidden="true" on focusable elements [New rule] disallow aria-hidden="true" on focusable elements Aug 26, 2022
@codecov
Copy link
Copy Markdown

codecov Bot commented Aug 26, 2022

Codecov Report

Merging #888 (42db006) into main (bbae2c4) will increase coverage by 0.00%.
The diff coverage is 100.00%.

❗ Current head 42db006 differs from pull request most recent head e22aef4. Consider uploading reports for the commit e22aef4 to get more accurate results

@@           Coverage Diff           @@
##             main     #888   +/-   ##
=======================================
  Coverage   99.29%   99.29%           
=======================================
  Files         101      103    +2     
  Lines        1555     1571   +16     
  Branches      511      514    +3     
=======================================
+ Hits         1544     1560   +16     
  Misses         11       11           
Impacted Files Coverage Δ
src/rules/no-aria-hidden-on-focusable.js 100.00% <100.00%> (ø)
src/util/isFocusable.js 100.00% <100.00%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

Copy link
Copy Markdown
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good overall.

Comment thread src/rules/no-aria-hidden-on-focusable.js Outdated
Comment thread src/rules/no-aria-hidden-on-focusable.js Outdated
Comment thread __tests__/src/rules/no-aria-hidden-on-focusable-test.js Outdated
@khiga8
Copy link
Copy Markdown
Contributor Author

khiga8 commented Aug 26, 2022

Added more test cases and fixed a bug in bc220ad.

Added a doc page in e96c9db.

@khiga8 khiga8 marked this pull request as ready for review August 26, 2022 23:26
Comment thread src/rules/no-aria-hidden-on-focusable.js Outdated
Comment thread src/rules/no-aria-hidden-on-focusable.js Outdated
@ljharb ljharb requested a review from jessebeach August 26, 2022 23:54
Copy link
Copy Markdown
Collaborator

@jessebeach jessebeach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@khiga8 this is a great idea for a rule. You're off to a solid start. I'd love to move your isFocusable function to its own utility. And I have a question about the reason behind traversing child elements.

I think you can get this to a place where we can get it committed. I look forward to the updates!

const schema = generateObjSchema();

/* Returns true when element is interactive and does not have `tabIndex=-1`, or when an element has a tabIndex that isn't -1. */
function isFocusable(type, attributes) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this function to https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/main/src/util and add tests for it? This is a great utility function to have.

It does need some work though, so I encourage you to set up test cases that enumerate all the possible scenarios. And keep in mind that authors will, in practice, use any and every integer for tabindex, including integers less than -1 and integers greater than zero. So your test cases should take these into account as well.

JSXOpeningElement(node) {
const { attributes } = node;
const type = elementType(node);
const ariaHidden = getPropValue(getProp(attributes, 'aria-hidden')) === true;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isHidden

const type = elementType(node);
const ariaHidden = getPropValue(getProp(attributes, 'aria-hidden')) === true;

if (!ariaHidden) return;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like this would be a bit more idiomatic written like this, what do you think?

if (isHidden && isFocusable) {
  // create the report side effect
}

message: errorMessage,
});
}
const { children } = node.parent;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, you want to use ESLint's visitor pattern to visit nodes, rather than doing your own tree traversal within a rule down into child nodes. What is purpose of creating reports for focusable children of the opening node?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing this out. I'm new to ESLint rule creation so uh.. please excuse my non-idiomatic code 🙇‍♀️.

My intention had been to cover cases where aria-hidden is set on an element that contains focusable children like:

<div aria-hidden="true">
   <a href="/">Some site</a>
</div>

Is this something that can be accomplished with the visitor pattern? I'll have to do a bit more digging into ESLint conventions.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intention had been to cover cases where aria-hidden is set on an element that contains focusable children like:

Ok, I understand. For this rule, I'm going to recommend that you keep it narrowly focused to just focusable elements and not their ancestors or descendants. The problem of the cascading effects of aria-hidden on the AX tree is a thorny one and it will complicate a rule that is otherwise straightforward and easy to reason about. Plus, these sorts of tree relationships are hard to lint for, because you're only getting a snippet of the total render tree while linting, so that incidence of positive cases is going to be low with this static linting method. The reason I really like your rule is that it can be narrowly focused to focusable elements and there is good literature about how aria-hidden should interact with these.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please excuse my non-idiomatic code 🙇‍♀️

No need for apologies :)

@khiga8 khiga8 marked this pull request as draft August 27, 2022 01:23
khiga8 added 2 commits August 29, 2022 21:47
Raise error when aria-hidden="true" is set on focusable element.
Exceptions are if `tabindex=-1`.

Fixes jsx-eslint#881
@khiga8 khiga8 marked this pull request as ready for review August 30, 2022 18:45
@khiga8 khiga8 requested a review from jessebeach August 31, 2022 15:22
Copy link
Copy Markdown
Collaborator

@jessebeach jessebeach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is amazing @khiga8! Thank you so much for proposing this rule and going through the iterations to develop it. Let's get it landed!

@jessebeach
Copy link
Copy Markdown
Collaborator

@ljharb ready to land

@ljharb ljharb force-pushed the kh-disallow-aria-hidden-on-interactive branch from 42db006 to e22aef4 Compare August 31, 2022 19:52
@ljharb ljharb merged commit e22aef4 into jsx-eslint:main Aug 31, 2022
@khiga8 khiga8 deleted the kh-disallow-aria-hidden-on-interactive branch August 31, 2022 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New rule: Don't allow aria-hidden on focusable elements

3 participants