Skip to content

Commit f5c36f0

Browse files
committed
rule: no-aria-hidden-on-focusable-elements
1 parent e9e435f commit f5c36f0

File tree

6 files changed

+132
-1
lines changed

6 files changed

+132
-1
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ linters:
3939
enabled: true
4040
GitHub::Accessibility::NestedInteractiveElementsCounter:
4141
enabled: true
42+
GitHub::Accessibility::NoAriaHiddenOnFocusableCounter:
43+
enabled: true
4244
GitHub::Accessibility::NoAriaLabelMisuseCounter:
4345
enabled: true
4446
GitHub::Accessibility::NoPositiveTabIndexCounter:
@@ -61,6 +63,7 @@ linters:
6163
- [GitHub::Accessibility::NestedInteractiveElementsCounter](./docs/rules/accessibility/nested-interactive-elements-counter.md)
6264
- [GitHub::Accessibility::IframeHasTitleCounter](./docs/rules/accessibility/iframe-has-title-counter.md)
6365
- [GitHub::Accessibility::ImageHasAltCounter](./docs/rules/accessibility/image-has-alt-counter.md)
66+
- [GitHub::Accessibility::NoAriaHiddenOnFocusableCounter](./docs/rules/accessibility/no-aria-hidden-on-focusable-counter.md)
6467
- [GitHub::Accessibility::NoAriaLabelMisuseCounter](./docs/rules/accessibility/no-aria-label-misuse-counter.md)
6568
- [GitHub::Accessibility::NoPositiveTabIndexCounter](./docs/rules/accessibility/no-positive-tab-index-counter.md)
6669
- [GitHub::Accessibility::NoRedundantImageAltCounter](./docs/rules/accessibility/no-redundant-image-alt-counter.md)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# No aria-hidden on focusable counter
2+
3+
## Rule Details
4+
5+
Elements that are focusable should not have `aria-hidden="true"` set.
6+
7+
`aria-hidden="true"` hides an element from assistive technologies, but if the element is still reachable by keyboard, it can cause confusion amongst assistie technology users who may be able to reach the element, but not have access to the element or it's information.
8+
9+
### Resources
10+
11+
- [Accessibility insights: aria-hidden-focus](https://accessibilityinsights.io/info-examples/web/aria-hidden-focus/)
12+
13+
## Examples
14+
15+
### **Incorrect** code for this rule 👎
16+
17+
```erb
18+
<button aria-hidden="true">Submit</button>
19+
```
20+
21+
```erb
22+
<div role="menuitem" aria-hidden="true" tabindex="0"></div>
23+
```
24+
25+
### **Correct** code for this rule 👍
26+
27+
```erb
28+
<button>Submit</button>
29+
```
30+
31+
```erb
32+
<div role="menuitem" aria-hidden="true" tabindex="-1"></div>
33+
```

lib/erblint-github/linters/custom_helpers.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
module ERBLint
77
module Linters
88
module CustomHelpers
9+
INTERACTIVE_ELEMENTS = %w[button summary input select textarea a].freeze
10+
911
def rule_disabled?(processed_source)
1012
processed_source.parser.ast.descendants(:erb).each do |node|
1113
indicator_node, _, code_node, = *node
@@ -89,6 +91,15 @@ def tags(processed_source)
8991
def simple_class_name
9092
self.class.name.gsub("ERBLint::Linters::", "")
9193
end
94+
95+
def focusable?(tag)
96+
tabindex = possible_attribute_values(tag, "tabindex")
97+
if INTERACTIVE_ELEMENTS.include?(tag.name)
98+
tabindex.empty? || tabindex.first.to_i >= 0
99+
else
100+
tabindex.any? && tabindex.first.to_i >= 0
101+
end
102+
end
92103
end
93104
end
94105
end

lib/erblint-github/linters/github/accessibility/nested_interactive_elements_counter.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ class NestedInteractiveElementsCounter < Linter
1010
include ERBLint::Linters::CustomHelpers
1111
include LinterRegistry
1212

13-
INTERACTIVE_ELEMENTS = %w[button summary input select textarea a].freeze
1413
MESSAGE = "Nesting interactive elements produces invalid HTML, and ssistive technologies, such as screen readers, might ignore or respond unexpectedly to such nested controls."
1514

1615
def run(processed_source)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../../custom_helpers"
4+
5+
module ERBLint
6+
module Linters
7+
module GitHub
8+
module Accessibility
9+
class NoAriaHiddenOnFocusableCounter < Linter
10+
include ERBLint::Linters::CustomHelpers
11+
include LinterRegistry
12+
13+
MESSAGE = "Elements that are focusable should not have `aria-hidden='true' because it can cause confusion on the state of the element for assistive technolgoy users."
14+
15+
def run(processed_source)
16+
tags(processed_source).each do |tag|
17+
aria_hidden = possible_attribute_values(tag, "aria-hidden")
18+
generate_offense(self.class, processed_source, tag) if aria_hidden.include?("true") && focusable?(tag)
19+
end
20+
21+
counter_correct?(processed_source)
22+
end
23+
24+
def autocorrect(processed_source, offense)
25+
return unless offense.context
26+
27+
lambda do |corrector|
28+
if processed_source.file_content.include?("erblint:counter #{simple_class_name}")
29+
# update the counter if exists
30+
corrector.replace(offense.source_range, offense.context)
31+
else
32+
# add comment with counter if none
33+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
34+
end
35+
end
36+
end
37+
end
38+
end
39+
end
40+
end
41+
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class NoAriaHiddenOnFocusableCounterTest < LinterTestCase
6+
def linter_class
7+
ERBLint::Linters::GitHub::Accessibility::NoAriaHiddenOnFocusableCounter
8+
end
9+
10+
def test_does_not_warn_if_link_does_not_have_aria_hidden
11+
@file = "<a href='github.com'>GitHub</a>"
12+
@linter.run(processed_source)
13+
14+
assert_empty @linter.offenses
15+
end
16+
17+
def test_does_not_warn_if_link_has_aria_hidden_false
18+
@file = "<a aria-hidden='false' href='github.com'>GitHub</a>"
19+
@linter.run(processed_source)
20+
21+
assert_empty @linter.offenses
22+
end
23+
24+
def test_warns_when_link_has_aria_hidden_true
25+
@file = "<a aria-hidden='true' href='github.com'>GitHub</a>"
26+
@linter.run(processed_source)
27+
28+
refute_empty @linter.offenses
29+
end
30+
31+
def test_does_not_warn_when_link_has_aria_hidden_true_and_is_not_focusable
32+
@file = "<a aria-hidden='true' tabindex='-1' href='github.com'>GitHub</a>"
33+
@linter.run(processed_source)
34+
35+
assert_empty @linter.offenses
36+
end
37+
38+
def test_warns_when_element_has_aria_hidden_true_and_is_tab_focusable
39+
@file = "<div role='list' aria-hidden='true' tabindex='0'></div>"
40+
@linter.run(processed_source)
41+
42+
refute_empty @linter.offenses
43+
end
44+
end

0 commit comments

Comments
 (0)