Repository navigation
Provide a way to avoid/solve React Native view flattening issues #319
Copy link
Copy link
Open
Labels
bug: react-nativeA bug that originates in React NativeA bug that originates in React Native
Description
Describe the feature request
This is a follow-up to #318 regarding the nativeProps feature request, in which @necolas raised a specific issue related to view flattening and the collapsable prop.
Brief Summary
- By default, React Native performs view flattening on both iOS and Android. This process removes "layout-only" views from the platform view tree to reduce the number of platform views created. In contrast, the web does not have a corresponding technique, as the DOM element tree is preserved as rendered.
- Layout-only views are views that handle layout-related properties like margins, padding, etc., which can be managed by Yoga and safely removed. However, views with properties like background color, opacity, borders, shadows, etc., cannot be flattened because these properties cannot be replaced by pure Yoga layout adjustments.
- View flattening distorts the platform view tree, both statically (by making views that were previously separate into siblings) and dynamically (when a view, along with all its children, transitions between being "layout-only" and an actual view).
Example of issues caused by view flattening
- Screen reader focus order: On iOS, the screen reader considers platform view grouping, reading text elements under the same parent view consecutively. With view flattening, views from different flattened containers can end up in the same container, resulting in an incorrect screen reader order (this is the issue I am currently facing).
- Validation error styling: For example, setting a red border or background around a view containing a text input to signal a validation error can cause the container view to change from "layout-only" to an actual view. This, in turn, can trigger the platform text input component to be recreated, resetting its intrinsic native state (e.g., cursor position). More details here.
- Community libraries: Libraries like React Native Gesture Handler sometimes need to attach to an actual native view. With view flattening, the required view might be removed, causing errors.
These are just a few examples of how view flattening can lead to unexpected and hard-to-diagnose issues, as it’s not always obvious which views are being removed.
Traditionally, the standard way to disable view flattening is by using the collapsable prop, which explicitly clarifies the developer's intent. However, this prop is not accessible in RSD. The proposal for nativeProps was intended to create an escape hatch to allow access to this or other similar props.
Potential solutions
- Disable view flattening in RSD altogether: This would align React Native's behavior with the DOM, where platform views are never flattened. However, this is a crude solution that would eliminate a generally useful optimization technique and could have performance implications.
- Encourage users to use React Native's
Viewinstead ofh.div, etc.: While possible, this approach requires users to maintain two sets of styles, RSD styles and React Native styles, which is a significant inconvenience. - Use another prop that disables view flattening as a side effect: For example, the
nativeIDprop (which RSD maps fromid) could be used. However, this approach lacks clarity of intent, and a future developer (or even our future selves) might mistakenly remove it, thinking the ID is unused.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bug: react-nativeA bug that originates in React NativeA bug that originates in React Native
Activity
MoOx commentedon Jun 17, 2025
Web have display: contents that mighty be helpful.
mdjastrzebski commentedon Jun 17, 2025
@MoOx, good catch that the web actually has something similar to view flattening. However, the key difference is that, on the web, this is an opt-in feature, whereas in React Native, view flattening it is opt-out. React Native also supports
display: contentsdirectly.That being said, I don’t see how
display: contentswould help with the default view flattening issues in React Native 🤷♂️necolas commentedon Jun 17, 2025
Thanks for this clear and thoughtful description of the problem and potential solutions.
I think this makes the most sense, although it is sure to raise some concerns about potential performance drawbacks. However, this would be a reliable way to avoid the issues associated with view flattening, especially if it is not sophisticated enough to account for things like screenreader order. Furthermore, I think there are only going to be more cases in the future where unexpected flattening on native is going to cause divergence from what is expected in existing web code/patterns, e.g., walking the DOM tree, reading event paths.
necolas commentedon Jun 17, 2025
I spoke with @rozele about this subject and we're going to look into a couple of things:
You might be interested in this piece of code that determines whether a stacking context is created: https://github.com/facebook/react-native/blob/fd29d68c162261b35bbfa25f4185e24ef5f4f5cb/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp#L49
Are there any specific accessibility props that we could add here which would fix the "Screen reader focus order" issue? Or is that an issue that can appear for Views that don't/shouldn't have accessibility props?
It also looks another way to disable view flattening (for now) is with certain CSS properties, like
isolation:isolate. That will apply to web too though, but at least it will result in consistent layout across platforms.mdjastrzebski commentedon Jun 18, 2025
I've prepared contrived example to showcase the issue:
The code
The effect (on iOS)
View.Flattening.vs.A11y.Order.mp4
In the first group (with default view flattening), the focus order is: Title 1 => Action 1 => Description 1.
However, if I disable view flattening on the view with style={styles.itemContent}, the focus order becomes the expected: Title 2 => Description 2 => Action 2.
A runnable repro is available here.
Considerations
ViewandTextcomponents with default props. No accessibility props are set.Textcomponents, elements withaccessibilityLabel, etc.). If a container contains 2+ readable elements, it should not be marked as layout-only.flexDirectionchanges. For example, in my case, a flattened container with a vertical layout is placed inside a container with a horizontal layout. However, this raises questions about other layout cases, such as absolute positioning.necolas commentedon Aug 6, 2025
I ran my Fantom benchmark with and withoutcollapsable: falseonView, and there was no significant difference in the results.TIL Fantom can't surface the perf benefits of view flattening as it doesn't really create native views.
collapsable: (not set)
collapsable:
falsemdjastrzebski commentedon Aug 12, 2025
@necolas to double check, are the tests you are using for perf comparison use styles that are "layout-only" and would trigger View Flattening?
necolas commentedon Aug 12, 2025
In this case the tests have a lot of different styles, some of which are preventing view flattening. I later created some other tests that did show a performance difference in Fantom when
collapsable={false}, so I think we can surface some of the perf benefit of view flattening via Fantom tests.compojoom commentedon Oct 31, 2025
View flattening is cool and weird at the same time. I'm writing maestro tests and need to rely on the structure. For example I target a text. Yet my assertVisible fails. When I look at the source I have ", Currency, USD, "
In my react code I'm placing the text currency inside of a
<Text/>component. Yet in the final viewCurrencyis not sitting alone in a RCTText, but somehow it is there with an icon, currency, usd and another icon.I was more expecting this layout
🤷 It feels like I'm playing a slot machine.