Inaccessible Action
This is an experimental rule, and therefore its result(s) are considered to be in beta testing. Learn more about experimental rules and how you can help improve them.
An interactive view should have the ability to be activated with assistive technology.
Impact
Assistive Technology users should be able to activate an active control's action using assistive technology.
Confirmation
- Turn on VoiceOver
- Focus on the element
- One of the following will happen:
- Inaccessible: The user will not be able to activate the active control's action using assistive technology.
- Accessible: The user will be able to use assistive technology to activate the active control.
How to Fix
An issue is found by this rule when:
- a control and its label are within a parent view marked as isAccessibilityElement = true
- the parent view does not have a separate gesture recognizer
and/or:
- the control is not in the middle of the parent view, therefore cannot be activated with VoiceOver
UIKit
In storyboard:
- Select the element with an
InaccessibleAction
issue - Ensure that the Inspectors Panel is visible
- Select the Identity Inspector
- Under Accessibility, there is a property called "Accessibility" with a checkbox called "Enabled" next to it. Ensure that the "Enabled" check box is selected for the active control
An alternative option is to change the element's accessibility path:
- Repeat steps 1-3 for the active control, its label and the view that contains the active control
- Under Accessibility, there is a property called "Accessibility" with a checkbox called "Enabled" next to it. Ensure that the "Enabled" check box is selected for the active control, but not for the label or its containing view
- In the code, change the accessibility path to include the containing view's active control and label as shown in the second step of the fourth item in the 'In code' section
Another solution is to have the active control use its parent's accessibility activation point:
- Select the element with an
InaccessibleAction
issue - Ensure that the Inspectors Panel is visible
- Select the active control in the element
- Select the Size Inspector
- Ensure that the active control is centered horizontally and vertically within its containing view
In code:
Find where the active control's isAccessibilityElement property was set and ensure that it is set to true.
buttonContainerView.isAccessibilityElement = true
Another option is to add a gesture recognizer to the active control's containing view. Find the view that contains the control and add a tap gesture recognizer.
buttonContainerView.addGestureRecognizer(UITapGestureRecognizer(target: self, selector: #selector(buttonTapped)))
Alternatively, you can change the active control to be in the center of the parent view, so the containing view's accessibilityActivationPoint activates the active control. Find the view that contains the control and ensure that the control is in the exact center of the parent view that contains it.
buttonContainerView.button.centerXAnchor.constraint(equalTo: buttonContainerView.centerXAnchor).isActive = true
buttonContainerView.button.centerYAnchor.constraint(equalTo: buttonContainerView.centerYAnchor).isActive = true
Additionally, you can fix this issue by changing the accessibility path to include the containing view's active control and label.
- Make the control's label and the parent view isAccessibilityElement = false
toggleContainerView.toggle.isAccessibilityElement = true
toggleContainerView.label.isAccessibilityElement = false
toggleContainerView.isAccessibilityElement = false
- Update the control's accessibility path to encapsulate both the label and the control
// 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 accessibility issue cannot occur on most default elements and active controls in SwiftUI; however, this issue can occur with some custom active controls such as a custom slider, stepper or toggle or if views are grouped together incorrectly.
When using custom active controls like a stepper, slider or toggle, consider using the .accessibilityRepresentation view modifier to make custom controls have the same accessibility representation as the corresponding default version of the control.
// In this scenario, add an .accessibilityRepresentation view modifier on a completely custom Toggle view
// Custom toggle element
HStack {
Text("Dark mode is \(getDarkModeStatus().localized)")
Image(systemName: getDarkModeImage())
}.onTapGesture {
isOn.toggle()
} // apply the .accessibilityRepresentation modifier to the custom toggle
.accessibilityRepresentation {
Toggle(isOn: $isOn, label: {
Text("Dark mode is \(getDarkModeStatus().localized)")
})
}
When grouping views together, there are a few different approaches that can be taken to ensure there are no inaccessible action issues, and to provide the best accessibility experience for a user.
If views are being grouped together, use the modifier .accessibilityElement(.combine) or .accessibilityElement(.contain) on the containing view to ensure that all actions associated with a child control are available to an assistive technology.
VStack(alignment: .leading) {
Text("Adjust settings below")
.accessibilityElement(children: .ignore) // We ignore this text and instead apply an accessibility label with the same content to the containing view
Divider()
Toggle(isOn: $isOn) {
Text("Dark Mode is \(toggleStateText())")
}
.accessibility(value: Text("Dark Mode is \(toggleStateText())")
}
.accessibilityElement(children: .combine)
.accessibilityLabel(Text("Adjust settings below"))
If the containing view has an adjustable control like a stepper or slider, consider using the .accessibilityAdjustableAction modifier to provide the best possible accessibility experience for the user. Additionally, if you use the .accessibilityElement(.ignore) modifier on a control within the containing view, be sure to provide the containing view with an accessibility action of the same functionality as the ignored control.
VStack {
Text("Adjust the stepper below to update dog petting data".localized)
Divider()
HStack {
Stepper(value: $value) {
Text("Total dogs pet today \($value.wrappedValue)")
}.accessibilityElement(children: .ignore)
}
}
.accessibilityElement(children: .combine)
.accessibilityValue(Text("\($value.wrappedValue)"))
.accessibilityAdjustableAction({ direction in
switch direction {
case .increment:
value += 1
case .decrement:
value -= 1
@unknown default:
print("unknown direction used")
}
})
}
React Native
This accessibility issue cannot occur on most touchable or pressable controls in React Native; however, this issue can occur with some custom active controls.
Option 1: Allow parent view to handle focus
Set the containing view's accessible
property to true and it's accessibilityElementsHidden
property to false. Assign the appropriate accessibilityRole
to match the expected behavior. These properties will allow the custom control to be activated by assistive technology.
<View
accessible={true}
accessibilityElementsHidden={false}
accessibilityRole='link'
accessibilityLabel='Learn more about Deque'
onTouchStart={openLink}
>
<Image
source={DequeLogo}
style={{ width: 100, height: 100 }}
/>
</View>
Option 2: Allow element to handle focus
On the control, set the accessible
property to true, the accessibilityElementsHidden
to false, and assign the appropriate accessibilityRole
to match the expected behavior. These properties will allow the custom control to be activated by assistive technology.
<Image
source={DequeLogo}
accessible={true}
accessibilityElementsHidden={false}
accessibilityRole='link'
accessibilityLabel='Learn more about Deque'
onTouchStart={openLink}
style={{ width: 100, height: 100 }}
/>