-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
117 changes: 117 additions & 0 deletions
117
accessibility-checker-engine/help-v4/en-US/element_tabbable_unobscured.html
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,117 @@ | ||
<html lang="en-US"> | ||
<head> | ||
<!-- | ||
/****************************************************************************** | ||
Copyright:: 2022- IBM, Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*****************************************************************************/ | ||
--> | ||
<!-- Title and messages generated at build time --> | ||
<link rel="icon" href="https://ibm.com/able/favicon-32x32.png" type="image/png"> | ||
<link rel="icon" href="https://ibm.com/able/favicon.svg" type="image/svg+xml"> | ||
<link rel="stylesheet" href="../common/help.css" /> | ||
<script type="module"> | ||
import "https://1.www.s81c.com/common/carbon/web-components/tag/latest/code-snippet.min.js"; | ||
import "https://1.www.s81c.com/common/carbon/web-components/tag/latest/list.min.js"; | ||
</script> | ||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | ||
<script src="../common/help.js"></script> | ||
</head> | ||
<body> | ||
<div class="bx--grid toolHelp"> | ||
<div class="bx--row"> | ||
<div class="bx--col-sm-4 bx--col-md-8 bx--col-lg-16 toolHead"> | ||
<!-- Group message injected here --> | ||
<h3 id="ruleMessage"></h3> | ||
<!-- Severity level injected here --> | ||
<div id="locLevel"></div> | ||
<!-- Rule specific message injected here --> | ||
<p id="groupLabel"></p> | ||
</div> | ||
</div> | ||
<div class="bx--row"> | ||
<div class="bx--col-sm-4 bx--col-md-5 bx--col-lg-8 toolMain"> | ||
<!-- Start main panel --> | ||
<mark-down><script type="text/plain"> | ||
|
||
### Why is this important? | ||
|
||
Keyboard users navigate through the operable elements on the page by pressing the Tab key. | ||
All sighted keyboard users (and those relying on the keyboard interface, such as voice-command and switch users) need to be able to see the element that has keyboard focus, | ||
otherwise, they have no context about where they are or what to do. | ||
|
||
An off-screen or invisible (zero-sized or clipped) keyboard-focusable element has been found. | ||
In some cases, the Checker can't detect where JavaScript is repositioning or resizing elements when they have keyboard focus. | ||
This requires some human review to confirm that the element should receive keyboard focus, and if so, doesn't remain off-screen or impossible to see when it does get keyboard focus. | ||
If it should not be operable with the keyboard and is not removed from the tab order, | ||
people using a screen reader will "see" the element even though it's invisible, which will cause confusion. | ||
|
||
Normally, keyboard-focusable (tabbable) elements should be visible, | ||
but there are situations where it is acceptable to "hide" and then reveal the element when it receives keyboard focus, | ||
as in the '**Skip to main content**' example below. | ||
|
||
<!-- This is where the code snippet is injected --> | ||
<div id="locSnippet"></div> | ||
|
||
### What to do | ||
|
||
- If users _should not_ be able to interact with this element using the keyboard, set the `tabindex="-1"` attribute to remove the element from the tab order | ||
- **Or**, if users _should_ be able to interact with this element using the keyboard, use the Tab key to confirm it is visible on screen when it has keyboard focus | ||
- **And**, if the interactive element is off-screen when it receives keyboard focus, use the pseudo class `:focus` or adjust the position style (see example) | ||
- **And**, ensure the focus is visible as specified in [2.4.7 Focus Visible](https://www.ibm.com/able/requirements/requirements/#2_4_7) | ||
|
||
|
||
Example: this "**_Skip to main content_**" tabbable element uses CSS positioning to ensure it is visible when the element has keyboard focus: | ||
|
||
``` | ||
<style> | ||
.skip { | ||
position: absolute; background-color: white; padding: 0.5rem; | ||
left: -1000px; | ||
} | ||
.skip:focus { | ||
left: 1rem; | ||
} | ||
</style> | ||
<a class="skip" href="#main">Skip to main content</a> | ||
``` | ||
|
||
</script></mark-down> | ||
<!-- End main panel --> | ||
<!-- This is where the rule id is injected --> | ||
<div id="ruleInfo"></div> | ||
</div> | ||
<div class="bx--col-sm-4 bx--col-md-3 bx--col-lg-4 toolSide"> | ||
<!-- Start side panel --> | ||
<mark-down><script type="text/plain"> | ||
|
||
### About this requirement | ||
|
||
* [2.4.7 Focus Visible](https://www.ibm.com/able/requirements/requirements/#2_4_7) | ||
* [Unit test - check tab order](https://www.ibm.com/able/toolkit/develop/considerations/unit-testing/#check-tab-order) | ||
* [Unit test - confirm component keyboard interaction](https://www.ibm.com/able/toolkit/develop/considerations/unit-testing/#confirm-component-keyboard-interaction) | ||
|
||
### Who does this affect? | ||
|
||
* People who rely on keyboard control | ||
* People with dexterity impairment using voice control or a keyboard-like switch device | ||
* People with low vision who use screen magnification with keyboard control | ||
* People using a screen reader, including blind, low vision and neurodivergent people | ||
* Neurodivergent people who use the keyboard | ||
* Older adults who use the keyboard | ||
|
||
</script></mark-down> | ||
<!-- End side panel --> | ||
</div> | ||
</div> | ||
</div> | ||
</body> | ||
</html> |
100 changes: 100 additions & 0 deletions
100
accessibility-checker-engine/src/v4/rules/element_tabbable_unobscured.ts
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,100 @@ | ||
/****************************************************************************** | ||
Copyright:: 2022- IBM, Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*****************************************************************************/ | ||
|
||
import { RPTUtil } from "../../v2/checker/accessibility/util/legacy"; | ||
import { getDefinedStyles, getComputedStyle } from "../util/CSSUtil"; | ||
import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy, RulePotential } from "../api/IRule"; | ||
import { eRulePolicy, eToolkitLevel } from "../api/IRule"; | ||
|
||
export let element_tabbable_unobscured: Rule = { | ||
id: "element_tabbable_unobscured", | ||
context: "dom:*", | ||
dependencies: [], | ||
help: { | ||
"en-US": { | ||
"group": "element_tabbable_unobscured.html", | ||
"pass": "element_tabbable_unobscured.html", | ||
"potential_visible": "element_tabbable_unobscured.html" | ||
} | ||
}, | ||
messages: { | ||
"en-US": { | ||
"group": "A tabbable element should be visible on the screen when it has keyboard focus", | ||
"pass": "The tabbable element is visible on the screen", | ||
"potential_visible": "Confirm the element should be tabbable, and is visible on the screen when it has keyboard focus" | ||
} | ||
}, | ||
rulesets: [{ | ||
id: ["WCAG_2_2"], | ||
num: ["2.4.11"], | ||
level: eRulePolicy.RECOMMENDATION, | ||
toolkitLevel: eToolkitLevel.LEVEL_THREE | ||
}], | ||
act: [], | ||
run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { | ||
const ruleContext = context["dom"].node as HTMLElement; | ||
if (!RPTUtil.isTabbable(ruleContext)) | ||
return null; | ||
|
||
const nodeName = ruleContext.nodeName.toLocaleLowerCase(); | ||
const bounds = context["dom"].bounds; | ||
//in case the bounds not available | ||
if (!bounds) return null; | ||
|
||
// defined styles only give the styles that changed | ||
const defined_styles = getDefinedStyles(ruleContext); | ||
const onfocus_styles = getDefinedStyles(ruleContext, ":focus"); | ||
|
||
if (bounds['height'] === 0 || bounds['width'] === 0 | ||
|| (defined_styles['position']==='absolute' && defined_styles['clip'] && defined_styles['clip'].replaceAll(' ', '')==='rect(0px,0px,0px,0px)' | ||
&& !onfocus_styles['clip'])) | ||
return RulePotential("potential_visible", []); | ||
|
||
if (bounds['top'] >= 0 && bounds['left'] >= 0) | ||
return RulePass("pass"); | ||
|
||
const default_styles = getComputedStyle(ruleContext); | ||
|
||
let top = bounds['top']; | ||
let left = bounds['left']; | ||
|
||
if (Object.keys(onfocus_styles).length === 0 ) { | ||
// no onfocus position change, but could be changed from js | ||
return RulePotential("potential_visible", []); | ||
} else { | ||
// with onfocus position change | ||
var positions = ['absolute', 'fixed']; | ||
if (typeof onfocus_styles['top'] !== 'undefined') { | ||
if (positions.includes(onfocus_styles['position']) || (typeof onfocus_styles['position'] === 'undefined' && positions.includes(default_styles['position']))) { | ||
top = onfocus_styles['top'].replace(/\D/g,''); | ||
} else { | ||
// the position is undefined and the parent's position is 'relative' | ||
top = Number.MIN_VALUE; | ||
} | ||
} | ||
if (typeof onfocus_styles['left'] !== 'undefined') { | ||
if (positions.includes(onfocus_styles['position']) || (typeof onfocus_styles['position'] === 'undefined' && positions.includes(default_styles['position']))) { | ||
left = onfocus_styles['left'].replace(/\D/g,''); | ||
} else { | ||
// the position is undefined and the parent's position is 'relative' | ||
left = Number.MIN_VALUE; | ||
} | ||
} | ||
} | ||
|
||
if (top >= 0 && left >= 0) | ||
return RulePass("pass"); | ||
else | ||
return RulePotential("potential_visible", []); | ||
} | ||
} |