Inaccessible Action

Link to Inaccessible Action copied to clipboard
Free Trial
note

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.

WCAG 2.0 - 2.1.1 A Impact - Critical

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

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

  1. Select the element with an InaccessibleAction issue
  2. Ensure that the Inspectors Panel is visible
  3. Select the Identity Inspector
  4. 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:

  1. Repeat steps 1-3 for the active control, its label and the view that contains the active control
  2. 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
  3. 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:

  1. Select the element with an InaccessibleAction issue
  2. Ensure that the Inspectors Panel is visible
  3. Select the active control in the element
  4. Select the Size Inspector
  5. 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.

  1. Make the control's label and the parent view isAccessibilityElement = false
toggleContainerView.toggle.isAccessibilityElement = true
toggleContainerView.label.isAccessibilityElement = false
toggleContainerView.isAccessibilityElement = false
  1. 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 }}
/>