Correctly label elements using React Native’s accessibilityRole
How to correctly label elements using React Native’s accessibilityRole
prop so they work on iOS.
There are several values that 'work':
- button
- header
- link
If an element is something other than text it probably requires semantic labelling to help a screen-reader user know what it is. Even though in react-native's source code it looks like they support many more values, in practice this is not the case.
Example
In the preceding example, the screen has a header ‘Be the first to know’ and 2 buttons. ‘Turn on notifications’ and ‘Maybe later’. The latter may not look like a button, but it performs an action within the app—closing the modal—so it is a button.
header
Text nodes which are either big and bold i.e. look like headings, should be marked with accessibilityRole="header"
.
Also, text nodes which act as headings but don’t necessarily look like it i.e. if they visually sit above another section and/or a block of text, should be marked as headers too.
button
A button is an interactive element that does something:
- if you tap it, it opens a dialog or closes one
- it expands or collapses hidden content
- it redirects you to another screen
It can look like a button or a link, but it should always look like it can be pressed/tapped.
link
A link is an interactive element that when tapped takes a user outside of the app. It can look like a button or it can look like a link, but it should also look like it can be pressed/tapped.
Notes
The accessibilityRole
prop from react-native translates to accessibilityTrait in iOS and these do not include some roles found in accessibilityRole
for example:
- menuitem
- radio
- checkbox
and these roles will not be read out by VoiceOver.
So if you create a component like this: <TouchableOpacity accessible accessibilityRole="checkbox" />
then it will not be announced as a checkbox
and it will not fallback to 'button' either. Therefore, it is better to use accessibilityRole="button" accessibilityState={checked}
. Where checked
equals {checked: boolean}
.
Further reading:
- the class definition for AccessibilityTraits in react-native - AccessibilityPrimitives.h
- the type definition for AccessibilityRole in react-native - ViewAccessibility.d.ts
React-native bug or Apple feature?
This comment (from Frameworks Engineer) implies that Apple want all touchable/pressable elements to be buttons and the idea of a 'radio button' does not really exist in their design patterns for iOS app.
Deciding which role to use
When in doubt as to what an element's accessibilityRole should be, I tend to open a first-party app on the iPhone and find a similar component and see how it is handled. If that app calls it a button or a header then that is a good indication that users may expect my app's similar component to have the same role.
Testing
When testing with Native Testing Library, unless you add the prop accessible
or accessible={true}
(they do the same thing but your app may having coding guidelines/linting that prefers one over the other) then calling screen.getByRole('button');
will not work. As a rule, it is a good idea to always add accessible
or accessible={true}
wherever you add an accessibilityRole
prop.