In React Native, why does a vertical ScrollView nested inside another vertical ScrollView not scroll on Android, and how do you fix it?
answer
- iOS nests by default
- Android-only prop, default false
- inner view needs a bounded height
- FlatList inside ScrollView logs an error
- different axes need nothing
basics
~20 sOn Android, React Native's ScrollView joins nested scrolling only when nestedScrollEnabled is true, so the outer view keeps the drag. Set nestedScrollEnabled on the inner ScrollView and give it a bounded height, or better, redesign to one scroller per axis.
solid answer
~50 siOS supports nested scrolling out of the box, but on Android a React Native `ScrollView` only cooperates with a parent scroller when `nestedScrollEnabled` is `true`; the prop is Android-only and defaults to `false`. Without it, the outer vertical `ScrollView` takes the drag and the inner one never moves. The fix is `nestedScrollEnabled` on the inner scroll view plus a **bounded height** for it, or it just grows to its content and has nothing to scroll. The deeper fix is usually design: one scroller per axis. A `FlatList` inside a same-orientation `ScrollView` makes React Native log an error that it can break windowing, because the outer view lets the list grow to its full height; put the extra content in the list's header or footer instead. Different axes — a horizontal carousel in a vertical page — need none of this.
code
tsx · 28 linesimport {ScrollView, StyleSheet, Text, View} from 'react-native';
export function AboutSettings({licenceText}: {licenceText: string}) {
return (
<ScrollView contentContainerStyle={styles.page}>
<Text style={styles.heading}>About</Text>
<Text>Version 4.2.0</Text>
<Text style={styles.heading}>Licence</Text>
{/* bounded height: the inner view needs its own range to scroll */}
<View style={styles.licenceBox}>
{/* Android-only prop; iOS nests scroll views by default */}
<ScrollView nestedScrollEnabled>
<Text>{licenceText}</Text>
</ScrollView>
</View>
<Text style={styles.heading}>Support</Text>
<Text>[email protected]</Text>
</ScrollView>
);
}
const styles = StyleSheet.create({
page: {padding: 16, gap: 8},
heading: {fontSize: 16, fontWeight: '600', marginTop: 16},
licenceBox: {height: 220, borderWidth: 1, borderColor: '#d4d4d8'},
});go deeper
Remember that the inner of two vertical ScrollViews needs nestedScrollEnabled on Android and a fixed or maximum height to scroll at all.
Explain the platform split: iOS nests scroll views by default, while React Native's Android ScrollView opts in through an Android-only prop that defaults to false.
Treat same-axis nesting as a structural problem: recognise the VirtualizedList nesting error, explain why it breaks windowing, and restructure into one scroller with header and footer content.
Set a screen-design rule of one scroller per axis, and review exceptions such as embedded documents or bottom-sheet pickers explicitly rather than letting nested lists spread.
## The symptom A settings screen embeds the licence text in a fixed-height box so that the rest of the page stays visible: an inner vertical `ScrollView` inside the page's outer vertical `ScrollView`. On iOS, dragging inside the box scrolls the licence text. On Android, dragging inside the box scrolls the **whole page** and the licence text never moves. ## Why it happens Both platforms have to decide which of two scroll views owns a vertical drag. - **iOS**: React Native's docs note that nested scrolling is supported by default on iOS. - **Android**: a React Native `ScrollView` takes part in nested scrolling only when the **`nestedScrollEnabled`** prop is set. It is **Android-only** and **defaults to `false`**. With it off, the outer scroll view claims the vertical drag and the inner one never receives it. There is one built-in exception: when a `refreshControl` is attached on Android, React Native wraps the scroll view in a swipe-refresh layout and turns `nestedScrollEnabled` on for that scroll view by default, so the scroll view gets the touch before the refresh layout. ## The minimal fix 1. Add `nestedScrollEnabled` to the **inner** `ScrollView`. 2. Give the inner scroll view a **bounded height** — a fixed `height`, a `maxHeight`, or a bounded parent. Without one, the inner view grows to fit its text and has no range of its own to scroll. 3. Test on an Android device or emulator; iOS will not show the problem. ## When the right fix is a redesign Nested same-axis scrolling is often a design smell. Two scrollers competing for one axis are hard to use on a phone, and they defeat list virtualization: - A `FlatList` (or any `VirtualizedList`) inside a plain `ScrollView` of the **same orientation** makes React Native log an error: VirtualizedLists "should never be nested inside plain ScrollViews with the same orientation because it can break windowing and other functionality", suggesting a VirtualizedList-backed container instead. - The reason is layout: the outer `ScrollView` lets the list grow to the full height of its rows, so the list no longer has a small viewport to window against and ends up rendering far more rows than a screenful. - The fix is to make the list the only scroller and move the extra content into it — the list's header and footer components exist for exactly that. | Layout | What to do | |---|---| | Vertical box inside a vertical page | `nestedScrollEnabled` on the inner view, bounded height; or show the text on its own screen | | `FlatList` below a header in a vertical page | drop the outer `ScrollView`; render the header inside the list | | Horizontal carousel inside a vertical page | nothing special; the axes do not compete | | Two long vertical lists on one screen | one list with sections, or tabs, not two nested scrollers | ## Diagnosing it quickly - If it works on iOS and fails on Android, suspect `nestedScrollEnabled` first. - If it fails on both, check that the inner view has a bounded height. - If the console shows the VirtualizedList nesting error, the problem is structural, not a missing prop. - `FlatList` inherits `ScrollView` props through `VirtualizedList`, so `nestedScrollEnabled` can also be passed to a short list that genuinely must sit inside another vertical scroller. ## Common mistakes - Setting `nestedScrollEnabled` and expecting iOS behaviour to change; the prop does nothing there. - Adding the prop but not a height, so the inner view is as tall as its content. - Silencing the VirtualizedList nesting error instead of restructuring, then shipping a list that renders most of its rows up front.
- Why does a FlatList inside a vertical ScrollView lose its virtualization?The outer `ScrollView` lets its content grow without a height limit, so the `FlatList` is laid out as tall as its rows and has no small viewport to window against. It renders far more rows than a screenful, which is what virtualization was meant to avoid. React Native logs an error for this nesting and points you to a VirtualizedList-backed container instead.
- Does a horizontal ScrollView inside a vertical ScrollView need nestedScrollEnabled?No. The two scroll views move along different axes, so a horizontal swipe goes to the carousel and a vertical drag to the page. The Android prop is about two scrollers competing for the same axis, and the VirtualizedList nesting error only fires for the same orientation.
saying these in an interview costs you the question
- nestedScrollEnabled is needed on iOS as well as Android.
- Adding nestedScrollEnabled is enough even if the inner view has no bounded height.
- A FlatList nested in a vertical ScrollView keeps its windowing.
- The VirtualizedList nesting error is harmless noise to suppress.
- A horizontal carousel inside a vertical page needs nestedScrollEnabled.