A11y Element Focus Box

Link to A11y Element Focus Box copied to clipboard
Best Practice Impact - Moderate

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

  1. Turn on VoiceOver
  2. Focus the element
  3. 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 or accessibilityFrame. 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>