Skip to content

Commit cb685d7

Browse files
Merge pull request #89 from github/add_visually_hidden_interactive_rule
Add visually hidden interactive rule
2 parents a061f11 + d88c7f0 commit cb685d7

File tree

5 files changed

+122
-2
lines changed

5 files changed

+122
-2
lines changed

config/accessibility.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ linters:
3232
enabled: true
3333
GitHub::Accessibility::SvgHasAccessibleText:
3434
enabled: true
35+
GitHub::Accessibility::NoVisuallyHiddenInteractiveElements:
36+
enabled: true
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# No visually hidden interactive elements
2+
3+
## Rule Details
4+
5+
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.
6+
7+
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).
8+
9+
### Why do we visually hide content?
10+
11+
Visually hiding content can be useful when you want to provide information specifically to screen reader users or other assistive technology users while keeping content hidden from sighted users.
12+
13+
Applying the following css will visually hide content while still making it accessible to screen reader users.
14+
15+
```css
16+
clip-path: inset(50%);
17+
height: 1px;
18+
overflow: hidden;
19+
position: absolute;
20+
white-space: nowrap;
21+
width: 1px;
22+
```
23+
24+
👎 Examples of **incorrect** code for this rule:
25+
26+
```jsx
27+
<button className="sr-only">Submit</button>
28+
```
29+
30+
👍 Examples of **correct** code for this rule:
31+
32+
```jsx
33+
<h2 className="sr-only">Welcome to GitHub</h2>
34+
```
35+
36+
## Version
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 NoVisuallyHiddenInteractiveElements < Linter
10+
include ERBLint::Linters::CustomHelpers
11+
include LinterRegistry
12+
INTERACTIVE_ELEMENTS = %w[a button summary select option textarea].freeze
13+
14+
MESSAGE = "Avoid visually hidding interactive elements. Visually hiding interactive elements can be confusing to sighted keyboard users as it appears their focus has been lost when they navigate to the hidden element"
15+
16+
def run(processed_source)
17+
visually_hidden = false
18+
19+
tags(processed_source).each do |tag|
20+
next if tag.closing?
21+
classes = possible_attribute_values(tag, "class")
22+
visually_hidden = true if classes.include?("sr-only")
23+
next unless classes.include?("sr-only") || visually_hidden
24+
if INTERACTIVE_ELEMENTS.include?(tag.name)
25+
generate_offense(self.class, processed_source, tag)
26+
end
27+
end
28+
end
29+
end
30+
end
31+
end
32+
end
33+
end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class NoVisuallyHiddenInteractiveElements < LinterTestCase
6+
def linter_class
7+
ERBLint::Linters::GitHub::Accessibility::NoVisuallyHiddenInteractiveElements
8+
end
9+
10+
def test_warns_if_element_is_interactive_and_visually_hidden
11+
@file = "<button class='sr-only'>Text</button>"
12+
@linter.run(processed_source)
13+
14+
assert_equal(1, @linter.offenses.count)
15+
error_messages = @linter.offenses.map(&:message).sort
16+
assert_match(/Avoid visually hidding interactive elements. Visually hiding interactive elements can be confusing to sighted keyboard users as it appears their focus has been lost when they navigate to the hidden element/, error_messages.last)
17+
end
18+
19+
def test_does_not_warn_if_element_is_not_interactive_and_visually_hidden
20+
@file = "<div class='sr-only'>Text</div>"
21+
@linter.run(processed_source)
22+
23+
assert_empty @linter.offenses
24+
end
25+
26+
27+
def test_does_not_warn_if_element_is_interactive_and_not_visually_hidden
28+
@file = "<button class='other'>Submit</button>"
29+
@linter.run(processed_source)
30+
31+
assert_empty @linter.offenses
32+
end
33+
34+
def test_does_not_warn_if_element_is_interactive_and_shown_on_focus
35+
@file = "<a class='other show-on-focus'>skip to main content</a>"
36+
@linter.run(processed_source)
37+
38+
assert_empty @linter.offenses
39+
end
40+
41+
def test_warn_if_element_is_interactive_in_a_visually_hidden_parent
42+
@file = "<div class='sr-only'><button>Submit</button></div>"
43+
@linter.run(processed_source)
44+
45+
assert_equal(1, @linter.offenses.count)
46+
error_messages = @linter.offenses.map(&:message).sort
47+
assert_match(/Avoid visually hidding interactive elements. Visually hiding interactive elements can be confusing to sighted keyboard users as it appears their focus has been lost when they navigate to the hidden element/, error_messages.last)
48+
end
49+
end

test/recommended_setup_works_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def test_asserts_recommended_setup_works
1616
end
1717
known_linter_names ||= ERBLint::LinterRegistry.linters.map(&:simple_name)
1818

19-
assert_equal 15, rules_enabled_in_accessibility_config
20-
assert_equal 15, known_linter_names.count { |linter| linter.include?("GitHub::Accessibility") }
19+
assert_equal 16, rules_enabled_in_accessibility_config
20+
assert_equal 16, known_linter_names.count { |linter| linter.include?("GitHub::Accessibility") }
2121
end
2222
end

0 commit comments

Comments
 (0)