Skip to content

Commit 554d1e4

Browse files
feat(wcag-2-2): Add Focus Not Obscured requirement (#7059)
#### Details This PR adds the Focus Not Obscured requirement to Assessment, including content for the requirement view and info pane. ![focus not obscured info pane](https://github.com/microsoft/accessibility-insights-web/assets/3230904/0d8be50d-abf1-420e-b500-f235bbc8c0fc) ![focus not obscured requirement view](https://github.com/microsoft/accessibility-insights-web/assets/3230904/0dc9cc91-784b-41f5-bf53-5d87f2f4d7dd) ![left navigation showing Focus not obscured as 3.6 in the Focus section](https://github.com/microsoft/accessibility-insights-web/assets/3230904/a5a0ab23-3d3e-4c84-8788-5372164ee99e) ##### Motivation feature work ##### Context Related to microsoft/accessibility-insights-docs#1854 in the docs repo, where the [example linked in info-examples](https://github.com/microsoft/accessibility-insights-web/pull/7059/files#diff-2184cb082513862a557a1ce74b8a405cfb8158278dd17c7853e18b37233becb8R72) content is hosted. #### Pull request checklist <!-- If a checklist item is not applicable to this change, write "n/a" in the checkbox --> - [n/a] Addresses an existing issue: #0000 - [x] Ran `yarn fastpass` - [x] Added/updated relevant unit test(s) (and ran `yarn test`) - [x] Verified code coverage for the changes made. Check coverage report at: `<rootDir>/test-results/unit/coverage` - [x] PR title *AND* final merge commit title both start with a semantic tag (`fix:`, `chore:`, `feat(feature-name):`, `refactor:`). See `CONTRIBUTING.md`. - [x] (UI changes only) Added screenshots/GIFs to description above - [n/a] (UI changes only) Verified usability with NVDA/JAWS --------- Co-authored-by: Bill Dengler <codeofdusk@gmail.com>
1 parent 00b7b92 commit 554d1e4

File tree

6 files changed

+377
-1
lines changed

6 files changed

+377
-1
lines changed

src/assessments/visible-focus-order/assessment.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
import { FocusNotObscured } from 'assessments/visible-focus-order/test-steps/focus-not-obscured';
45
import { Messages } from 'common/messages';
56
import { VisualizationType } from 'common/types/visualization-type';
67
import { test as content } from 'content/test';
@@ -37,7 +38,14 @@ export const VisibleFocusOrderAssessment: Assessment = AssessmentBuilder.Assiste
3738
gettingStarted: keyboardInteractionGettingStarted,
3839
guidance,
3940
visualizationType: VisualizationType.VisibleFocusOrderAssessment,
40-
requirements: [VisibleFocus, RevealingContent, ModalDialogs, ClosingContent, FocusOrder],
41+
requirements: [
42+
VisibleFocus,
43+
RevealingContent,
44+
ModalDialogs,
45+
ClosingContent,
46+
FocusOrder,
47+
FocusNotObscured,
48+
],
4149
visualizationConfiguration: {
4250
key: key,
4351
analyzerProgressMessageType: Messages.Assessment.TabbedElementAdded,
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
import { generateUID } from 'common/uid-generator';
4+
import { link } from 'content/link';
5+
import { title } from 'content/strings/application';
6+
import * as content from 'content/test/focus/focus-not-obscured';
7+
import { RestartScanVisualHelperToggle } from 'DetailsView/components/restart-scan-visual-helper-toggle';
8+
import { FocusAnalyzerConfiguration } from 'injected/analyzers/analyzer';
9+
import { AnalyzerProvider } from 'injected/analyzers/analyzer-provider';
10+
import { VisualizationInstanceProcessor } from 'injected/visualization-instance-processor';
11+
import * as React from 'react';
12+
import { ManualTestRecordYourResults } from '../../common/manual-test-record-your-results';
13+
import * as Markup from '../../markup';
14+
import { Requirement } from '../../types/requirement';
15+
import { visibleFfocusOrderTestStep } from './test-steps';
16+
17+
const description: JSX.Element = (
18+
<span>
19+
For elements receiving keyboard focus, its focus indicator must be at least partially
20+
visible and not obscured by author-created content which overlays it, unless the focused
21+
element can be revealed without requiring the user to advance focus in the UI.
22+
</span>
23+
);
24+
25+
const howToTest: JSX.Element = (
26+
<div>
27+
<p>
28+
<Markup.Term>The visual helper for this requirement</Markup.Term> records elements in
29+
the target page that receive the input focus.
30+
</p>
31+
<p>
32+
<Markup.Term>Note:</Markup.Term> the AAA criterion Focus Not Obscured (Enhanced) calls
33+
for focusable elements to be entirely unobscured when receiving keyboard focus.
34+
</p>
35+
<p>
36+
<Markup.Term>Note:</Markup.Term> this rule covers standard keyboard focus. It does not
37+
pertain to “focus indicators” that screen readers can provide to illustrate where the
38+
assistive technology is presently reading.
39+
</p>
40+
<ol>
41+
<li>
42+
Use the keyboard to navigate through all the interactive interface components in the
43+
target page.
44+
<ol>
45+
<li>
46+
Use <Markup.Term>Tab</Markup.Term> and <Markup.Term>Shift+Tab</Markup.Term>{' '}
47+
to navigate between widgets both forwards and backwards.
48+
</li>
49+
<li>
50+
Use the arrow keys to navigate between the focusable elements within a
51+
composite widget.
52+
</li>
53+
</ol>
54+
</li>
55+
<li>
56+
As you move focus to each component, verify that the focused element is not
57+
completely obscured by other content. (In addition to the circle drawn by {title}.)
58+
<p>
59+
<Markup.Emphasis>
60+
Note: Focus can be obscured by user rendered content and still pass this
61+
requirement if that content can be dismissed via a keyboard command (e.g.,
62+
pressing the Escape key).
63+
</Markup.Emphasis>
64+
</p>
65+
</li>
66+
<ManualTestRecordYourResults isMultipleFailurePossible={true} />
67+
</ol>
68+
</div>
69+
);
70+
71+
export const FocusNotObscured: Requirement = {
72+
key: visibleFfocusOrderTestStep.focusNotObscured,
73+
name: 'Focus not obscured',
74+
description,
75+
howToTest,
76+
isManual: true,
77+
guidanceLinks: [link.WCAG_2_4_11],
78+
...content,
79+
getAnalyzer: (provider: AnalyzerProvider, analyzerConfig: FocusAnalyzerConfiguration) => {
80+
return provider.createFocusTrackingAnalyzer(analyzerConfig);
81+
},
82+
getVisualHelperToggle: props => <RestartScanVisualHelperToggle {...props} />,
83+
visualizationInstanceProcessor: VisualizationInstanceProcessor.addOrder,
84+
doNotScanByDefault: true,
85+
getDrawer: provider =>
86+
provider.createSVGDrawer({
87+
tabIndexLabel: {
88+
showTabIndexedLabel: false,
89+
},
90+
line: {
91+
showSolidFocusLine: false,
92+
},
93+
}),
94+
getNotificationMessage: selectorMap => 'Start pressing Tab to start visualizing tab stops.',
95+
switchToTargetTabOnScan: true,
96+
generateInstanceIdentifier: generateUID,
97+
};

src/assessments/visible-focus-order/test-steps/test-steps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export const enum visibleFfocusOrderTestStep {
66
modalDialogs = 'modal-dialogs',
77
revealingContent = 'revealing-content',
88
visibleFocus = 'visible-focus',
9+
focusNotObscured = 'focus-not-obscured',
910
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
import { create, React } from '../../common';
4+
5+
export const infoAndExamples = create(({ Markup }) => (
6+
<>
7+
<p>
8+
For elements receiving keyboard focus, its focus indicator must be at least partially visible and not obscured by author-created
9+
content which overlays it, unless the focused element can be revealed without requiring the user to advance focus in the UI.
10+
</p>
11+
12+
<h2>Why it matters</h2>
13+
<p>
14+
Keyboard users need to know which component currently has the input focus so they can predict the results of their key presses.
15+
If this failure occurs, users cannot see where the indicator is due to other authored content. Any 'sticky' or 'fixed' content
16+
that persistently remains visible when scrolling the viewport can potentially obscure other elements on the page, including
17+
controls the user may move keyboard focus to, for instance, by use of the Tab key.
18+
</p>
19+
<h3>
20+
<Markup.Term>From a user's perspective:</Markup.Term>
21+
</h3>
22+
<p>
23+
<Markup.Emphasis>
24+
"I'm a reporter with repetitive stress injury who uses speech recognition software. This page has a big content area that's
25+
always displayed across the bottom of the screen (a sticky footer). When I move focus to items, some are hidden behind the
26+
footer, and I can't see them. This page also uses a persistent header (also called a 'sticky' header or banner), where the
27+
header remains in the same place as I scroll down the screen. This header is obscuring elements that have focus - which is
28+
extremely annoying and blocking for me!"
29+
</Markup.Emphasis>
30+
</p>
31+
32+
<h2>How to fix</h2>
33+
<p>
34+
Ensure interactive components that receive focus indicator are at least partially visible and not obscured by author-created
35+
content which overlays it, unless the focused element can be revealed without requiring the user to advance focus in the UI.
36+
</p>
37+
38+
<h2>Example</h2>
39+
<Markup.PassFail
40+
failText={
41+
<p>
42+
A non-movable panel such as a sticky footer, cookie banner or chat widget can overlay content on the screen. These
43+
elements fail this rule if focus can be moved to an element where content would fully obscure to the point of being
44+
illegible and could not be scrolled into view (e.g., by attempting to manipulate the content in the visible viewport by
45+
use of arrow keys).
46+
</p>
47+
}
48+
passText={
49+
<p>
50+
To fix this issue, ensure that there is either enough scroll padding available so the user may initially find focus
51+
obscured by an element when navigating with the Tab key, but by adjusting the scroll position of the viewport, the
52+
obscured content can be made visible. Or, that the overlaying elements can be dismissed without a user having to move
53+
focus from the obscured element.
54+
</p>
55+
}
56+
/>
57+
58+
<h2>More examples</h2>
59+
60+
<h3>WCAG success criteria</h3>
61+
<Markup.Links>
62+
<Markup.HyperLink href="https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum">
63+
Understanding Success Criterion 2.4.11: Focus Not Obscured (Minimum)
64+
</Markup.HyperLink>
65+
</Markup.Links>
66+
67+
<h3>Sufficient techniques</h3>
68+
<Markup.Links>
69+
<Markup.HyperLink href="https://www.w3.org/WAI/WCAG22/Techniques/css/C43">
70+
Using CSS margin and scroll-margin to un-obscure content
71+
</Markup.HyperLink>
72+
<Markup.HyperLink href="https://accessibilityinsights.io/code-examples/focus-not-obscured/">
73+
View a passing example of WCAG 2.4.11 Focus Not Obscured using the popover attribute
74+
</Markup.HyperLink>
75+
</Markup.Links>
76+
77+
<h3>Additional Guidance</h3>
78+
<Markup.Links>
79+
<Markup.HyperLink href="https://www.w3.org/WAI/WCAG22/Techniques/failures/F110">
80+
Failure of Success Criterion 2.4.12 Focus Not Obscured (Minimum) due to a sticky footer or header completely hiding focused
81+
elements
82+
</Markup.HyperLink>
83+
</Markup.Links>
84+
</>
85+
));

src/content/test/focus/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33
import * as closingContent from './closing-content';
4+
import * as focusNotObscured from './focus-not-obscured';
45
import { infoAndExamples as focusOrder } from './focus-order';
56
import { guidance } from './guidance';
67
import * as modalDialogs from './modal-dialogs';
@@ -14,4 +15,5 @@ export const focus = {
1415
modalDialogs,
1516
closingContent,
1617
focusOrder,
18+
focusNotObscured,
1719
};

0 commit comments

Comments
 (0)