A11y Element Focus Box
Ensures a view's accessibility path, or VoiceOver focus box, encapsulates its own visual on-screen frame.
Impact
People using VoiceOver with sight are most impacted. VoiceOver will be announcing the details of one on-screen element, but the focus will appear partially or entirely off the element being announced.
Confirmation
- Turn on VoiceOver
- Focus the element
- One of the following will happen:
- Inaccessible: VoiceOver's focus box will partially contain the element.
- Inaccessible: VoiceOver's focus box will not contain the element at all.
- Accessible: VoiceOver's focus box will fully contain the element.
How to Fix
UIKit
Incorrect usage of the accessibilityPath
or accessibilityFrame
on a view will result in this rule finding an issue. Fix in one of two ways:
- Do not use
accessibilityPath
oraccessibilityFrame
. VoiceOver automatically calculates the on-screen coordinates of the view and should correctly draw a border (VoiceOver's focus box) around the currently-focused element. Using either of these accessibility APIs on an element will re-draw VoiceOver's focus box for that element. - If you must re-draw VoiceOver's focus box, ensure that you are correctly calculating the on-screen coordinates of the element and that you are recalculating and redefining the focus box every time the element moves (such as in a UIScrollView):
// Assuming we are in a ViewController
let button = UIButton()
// If not within a ViewController, self.view should be replaced with the rootView of the screen
let rootview = self.view
let onScreenFrame = button.superview!.convert(button.frame, to: rootview)
button.accessibilityPath = UIBezierPath(rect: onScreenFrame)
SwiftUI
This type of accessibility issue is not expected to occur within SwiftUI views.
React Native
This type of accessibility issue is not expected to occur with default Touchable or Pressable elements.
When adding focus to another type of element, add the accessible
prop, and the accessibilityElementsHidden
prop to the element directly:
<Image
source={DequeLogo}
accessible={true}
accessibilityElementsHidden={false}
accessibilityLabel="Deque Systems Logo"
accessibilityRole="image"
style={{ width: 100, height: 60 }}
resizeMode='center'
/>
When elements are grouped together within a containing view, add the accessible
prop, and the accessibilityElementsHidden
prop to the containing view:
<View
style={styles.rowContainer}
accessible={true}
accessibilityElementsHidden={false}
accessibilityLabel="Dark Mode"
accessibilityValue={{ text: "" + secondSwitchIsEnabled }}
accessibilityRole="switch"
onTouchStart={() => {
setSecondSwitchIsEnabled(!secondSwitchIsEnabled)
}}>
<Text style={{ fontSize: 18 }}>Dark Mode</Text>
<Switch
style={styles.standardSwitch}
importantForAccessibility='no-hide-descendants'
value={secondSwitchIsEnabled}
onValueChange={() => {
setSecondSwitchIsEnabled(!secondSwitchIsEnabled);
}}
/>
</View>