-
Couldn't load subscription status.
- Fork 24.9k
feat: handle nested accessible items on iOS #54277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: handle nested accessible items on iOS #54277
Conversation
|
Hey @coolsoftwaretyler! Thank you for looking into this. Accessibility in React Native is a pretty hard problem! This issue you are mentioning with children of accessibilityContainers not being accessible I think is solved by a prop that's currently experimental With this prop you can set in which order the children of a given View should be visited by VoiceOver/Talkback. This maps (almost?) 1:1 with the I think this proposal is interesting since it essentially let's us "opt-out" a view of being an accessibilityContainer, but other than it being a more convenient way to do this when you want to preserve the default ax order, I don't think we'd want to go this way since I'd recommend checking out Happy to discuss this more though! |
|
Hey @jorge-cab - that's awesome! I will definitely check it out this week. Thanks for the feedback. Glad to see React Native moving forward with a solution here. |
|
Hey @jorge-cab - is there any documentation on how I could use I tried setting <View style={styles.row}>
<Pressable
accessibilityLabel="Outer card button"
accessibilityRole="button"
experimental_accessibilityOrder={0}
onPress={() => setOuterPressCount(outerPressCount + 1)}
style={{
backgroundColor: '#f9c2ff',
padding: 16,
borderRadius: 8,
}}>
<Text>Outer Pressable</Text>
<Pressable
accessibilityLabel="Inner button"
accessibilityRole="button"
experimental_accessibilityOrder={1}
onPress={() => setInnerPressCount(innerPressCount + 1)}
style={{
backgroundColor: '#61dafb',
padding: 12,
marginTop: 8,
borderRadius: 6,
}}>
<Text>Inner Pressable</Text>
</Pressable>
</Pressable>
</View>But I'm not able to focus the inner pressable. Here's what I'm seeing with accessibility inspector: Screen.Recording.2025-10-27.at.9.08.14.AM.movAnd I've got the same issues on a physical device (in this example I keep swiping to the right, but never get to focus the inner pressable): video1897589165.mp4 |
|
Oh yeah sorry about that, you can check out the example on RNTester here react-native/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js Line 1663 in f9e3db5
You need to set a |
|
@coolsoftwaretyler @jorge-cab I actually did add documentation recently: https://reactnative.dev/docs/view#experimental_accessibilityorder. This is an experimental API, so it might be disabled for you (and is by default unless you turn it on). If that is the case, https://reactnative.dev/docs/next/releases/release-levels should help you figure out how to enable it. |
|
ok read through the rest of the conversations here. I agree with @jorge-cab that
The hardest part here is getting a focus order which matches iOS, since focus order is not always the most intuitive (you can take a look at how android does this in AOSP if you dare 😄). That being said, we could just change React Native to have its own axioms revolving focus order, and just do this ourselves with the layout information available in the shadow tree. Then rolling that out would be a nightmare - we would likely change quite a few app's focus order of some elements. If we keep this as opt in then we have a situation where sometimes, the UI tree focus's elements in a certain way, and sometimes in a different way. Its not the worst, and not impossible, just quite challenging to roll out safely and make a case for that work. |
Summary:
This is an initial idea for how we might solve a longstanding accessibility pain point on iOS: #24515.
Right now, if you write some React Native code like this:
One of the
Pressableitems will be inaccessible via VoiceOver on iOS. You can't select and interact with both. It works fine on TalkBack on Android, which discovers them independent of one another.You can fix this manually by using the
accessibilityActionsandonAccessibilityActionprops, like this:This works OK, but you have to know to do it, and then take the time to do it well. A lot of developers don't know about this issue, and end up degrading their user's experience because of it. Overall, it means React Native apps start on the back foot as far as accessibility goes. I think we could improve the experience of React Native apps broadly by offering a better built-in behavior.
iOS does have a concept of accessibility containers. I think we can leverage that to improve the experience. Thanks to @lindboe for teaching me about accessibility containers (and about this problem in general).
My initial proposal is this:
accessibilityContainer, iOS-only prop.falseby default, to preserve backwards compatibility, and to make this behavior opt-in. That way, we don't make every single view try to collect all of its children, unless a developer opts in to it (for now). And we also avoid the scenario where this breaks people who are usingaccessibilityActions(I'm not sure how these props might conflict).accessibilityContainerto a truthy value, it makes child interactive elements independently accessible by adding them to_accessibilityElements, along with a proxy for the container itself (so we can toss this prop on a wrapperPressablewithout requiring additional wrapperViewcomponents or anything)In an ideal world, I think this prop should be
trueby default, so devs don't have to know to do this at all, and they'd get a better default experience. But I'm not sure what the performance implications are for it since we have to parse all the subviews. Maybe it's not too bad, but I mostly want to get the conversation started, I'm not married to a given implementation.I hope a change like this might make the default React Native experience more accessible for many users.
Changelog:
[IOS] [ADDED] - accessibilityContainer prop to allow easier accessibility grouping
Test Plan:
rn-testerapp withcd packages/rn-tester && yarn prepare-iosVideos
Physical device/VoiceOver
Here's the fixed behavior with
accessibilityContainer={true}on a physical device:video1110878829.mp4
And here's how it behaves when the prop is
false(current behavior):video1009449806.mp4
Simulator/Accessibility Inspector
Here's a video with a simulator, you can see the change when I toggle the prop:
Screen.Recording.2025-10-25.at.8.35.15.PM.mov