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

A screenshot of a screen in the Eurostar iOS App (taken in 2024)

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:

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.

Want to work with us?

Let’s talk about your project