[Bug]: Closed sheet's backdrop stays pointerEvents 'auto' and blocks all touches on devices with a fractional dp screen height (animatedIndex = -0.99999998, never -1)

Author: khemraj-astroCreated Sep 8, 2026Updated Sep 8, 2026

Version

v5

Reanimated Version

v3

Gesture Handler Version

v2

Platforms

Android

What happened?

A permanently mounted, closed BottomSheet (index={-1}) with a BottomSheetBackdrop leaves an invisible, full-screen, pointerEvents: 'auto' backdrop on top of the whole screen on any device whose screen height in dp is not an integer — e.g. a Samsung Galaxy S23 Ultra / S21 FE at a non-default system "Display size" (1080×2340 @ density 2.625 → 891.4285… dp). Every React Native touch underneath it dies: Pressable, Switch, TextInput focus, ScrollView/FlatList scrolling, native pickers. gesture-handler controls keep working, because GH's own hit-testing skips views with alpha < 0.1 — which is why this got misdiagnosed for months as a React Native density bug (facebook/react-native#58360, now redirected here).

This is the same bug as #2680 ("Invisible closed backdrop can block touches after cold start on some Android devices", closed as stale — a commenter there already observed animatedIndex going to -0.9999… instead of -1) and the same "stale initial position" family as #2746. Here is the root cause with numbers.

Measured (5.2.9, but the code path is identical on master / 5.2.14) on the S23 Ultra at Display size 2.625, every mounted closed sheet after layout:

index=-0.9999999782638337  position=891.4285714285714
  • position = Dimensions.get('screen').height (5.2.9: INITIAL_POSITION = SCREEN_HEIGHT; master line 218 uses Dimensions.get('window').height) = 2340 / 2.625 computed as a JS double891.4285714285714.
  • The container height the index math uses (adjustedSnapPoints.push(containerHeight) in the animatedIndex derived value) comes from the container's onLayout → a float32 from the native layout → 891.4285888671875.
  • The two differ by ~1.7e-5 dp. Because a sheet that is never opened is never animated, animatedPosition keeps the stale initial constant and never gets re-synced to closedDetentPosition. interpolate(position, [..., containerHeight], [..., -1], CLAMP) therefore lands at -0.99999998, not -1.
  • BottomSheetBackdrop gates touchability with an exact comparison, animatedIndex.value <= disappearsOnIndex (line 123). -0.99999998 <= -1 is false → pointerEvents stays 'auto' on a fully transparent full-screen view. animatedSheetState (animatedPosition.value >= closedDetentPosition) is likewise never CLOSED.
  • On devices with an integer dp height (1080×2340 @ 3 → 780 dp) the double and the float32 are equal, the index is exactly -1, and everything works — hence "only some devices".

Why the invisible backdrop kills native controls too, not just JS pressables: RN's TouchTargetHelper picks it as the JS touch target (it is the top-most view under the finger with pointerEvents: auto), and on Android a ReactViewGroup with pointerEvents: auto returns true from onTouchEvent, so it also consumes the native touch before Switch/EditText/ScrollView beneath it ever see it.

Affected in practice: any Android device where Dimensions.get('screen').height is fractional. Samsung's Display-size presets produce densities like 2.625 and 2.8125 on 1080-wide panels (2340/2.625, 2400/2.8125 = 853.33 — the device in #2746). It is silent, app-wide, and looks like "the app is frozen except gesture-handler buttons".

Suggested fix (any one of these closes the gap; the first two are the real fix):

  1. Re-sync animatedPosition to closedDetentPosition whenever detents are (re)computed while the sheet is closed (index === -1 / never opened), so the closed position and the container height are the same float.
  2. Or initialise the position from the measured layout rather than a Dimensions constant (the constant is also what produces the status-bar peek in #2746).
  3. Defensively, gate the backdrop with a tolerance (animatedIndex.value <= disappearsOnIndex + 1e-3) or on animatedSheetState === CLOSED, instead of an exact float comparison.

Workaround we ship (in case it helps others): wrap the backdrop in a View whose pointerEvents is 'none' unless our own controlled visible state says the sheet is open or still animating closed. The disappearsOnIndex={-0.5} trick from #2680 also works.

Reproduction steps

  • Use a device (or emulator) whose screen height in dp is not an integer. Emulator: any 1080×2340 skin, then adb shell wm density 420 (→ 891.43 dp). Real device: Samsung Galaxy S23 Ultra or S21 FE, Settings → Display → Screen zoom at any non-default position.
  • Run the Snack below. It mounts a closed BottomSheet (index={-1}, snapPoints={['50%']}) with BottomSheetBackdrop (appearsOnIndex={0}, disappearsOnIndex={-1}) and shows the backdrop's animatedIndex / animatedPosition plus a plain RN Button and Switch.
  • Observe the panel: animatedIndex = -0.99999998, backdrop pointerEvents = 'auto'. Tap the Button, flip the Switch: nothing happens.
  • Set the density back to the panel default (adb shell wm density reset, or Screen zoom to default) and relaunch: animatedIndex = -1, pointerEvents = 'none', Button and Switch work.

Reproduction sample

https://snack.expo.dev/?platform=android&name=gorhom%20closed-backdrop%20fractional-dp%20repro&dependencies=%40gorhom%2Fbottom-sheet%40%2A%2Creact-native-reanimated%40%2A%2Creact-native-gesture-handler%40%2A&code=%2F%2F%20Repro%3A%20a%20CLOSED%20BottomSheet%27s%20invisible%20backdrop%20swallows%20every%20touch%20when%20the%0A%2F%2F%20device%27s%20screen%20height%20in%20dp%20is%20NOT%20an%20integer%20%28height_px%20%2F%20density%29%2C%20e.g.%20a%0A%2F%2F%20Samsung%20at%20a%20non-default%20%22Display%20size%22%20%281080x2340%20%40%202.625%20-%3E%20891.4285%E2%80%A6dp%29%2C%0A%2F%2F%20or%20any%20emulator%20with%20a%201080x2340%20skin%20after%20%60adb%20shell%20wm%20density%20420%60.%0A%2F%2F%0A%2F%2F%201.%20Run%20on%20such%20a%20device.%20The%20RN%20Button%20and%20Switch%20above%20the%20%28closed%29%20sheet%0A%2F%2F%20%20%20%20are%20dead%3B%20gesture-handler%20controls%20would%20keep%20working.%0A%2F%2F%202.%20The%20panel%20shows%20the%20closed%20sheet%27s%20animatedIndex%20%3D%20-0.99999998%20%28not%20-1%29%2C%0A%2F%2F%20%20%20%20so%20BottomSheetBackdrop%20keeps%20pointerEvents%20%27auto%27%20on%20a%20full-screen%2C%0A%2F%2F%20%20%20%20fully%20transparent%20view.%0A%2F%2F%203.%20On%20a%20device%20whose%20dp%20height%20is%20an%20integer%20%28e.g.%201080x2340%20%40%203%20-%3E%20780dp%29%0A%2F%2F%20%20%20%20the%20index%20is%20exactly%20-1%20and%20everything%20works.%0Aimport%20React%2C%20%7B%20useCallback%2C%20useRef%2C%20useState%20%7D%20from%20%27react%27%3B%0Aimport%20%7B%20Button%2C%20Dimensions%2C%20StyleSheet%2C%20Switch%2C%20Text%2C%20View%20%7D%20from%20%27react-native%27%3B%0Aimport%20%7B%20GestureHandlerRootView%20%7D%20from%20%27react-native-gesture-handler%27%3B%0Aimport%20%7B%20runOnJS%2C%20useAnimatedReaction%20%7D%20from%20%27react-native-reanimated%27%3B%0Aimport%20BottomSheet%2C%20%7B%20BottomSheetBackdrop%2C%20BottomSheetView%20%7D%20from%20%27%40gorhom%2Fbottom-sheet%27%3B%0A%0Afunction%20Probe%28%7B%20animatedIndex%2C%20animatedPosition%2C%20onValue%20%7D%29%20%7B%0A%20%20useAnimatedReaction%28%0A%20%20%20%20%28%29%20%3D%3E%20%5BanimatedIndex.value%2C%20animatedPosition.value%5D%2C%0A%20%20%20%20%28%5Bi%2C%20p%5D%29%20%3D%3E%20runOnJS%28onValue%29%28i%2C%20p%29%2C%0A%20%20%29%3B%0A%20%20return%20null%3B%0A%7D%0A%0Aexport%20default%20function%20App%28%29%20%7B%0A%20%20const%20ref%20%3D%20useRef%28null%29%3B%0A%20%20const%20%5Btaps%2C%20setTaps%5D%20%3D%20useState%280%29%3B%0A%20%20const%20%5Bon%2C%20setOn%5D%20%3D%20useState%28false%29%3B%0A%20%20const%20%5Bprobe%2C%20setProbe%5D%20%3D%20useState%28%7B%20index%3A%20NaN%2C%20position%3A%20NaN%20%7D%29%3B%0A%20%20const%20renderBackdrop%20%3D%20useCallback%28%0A%20%20%20%20props%20%3D%3E%20%28%0A%20%20%20%20%20%20%3C%3E%0A%20%20%20%20%20%20%20%20%3CProbe%20%7B...props%7D%20onValue%3D%7B%28index%2C%20position%29%20%3D%3E%20setProbe%28%7B%20index%2C%20position%20%7D%29%7D%20%2F%3E%0A%20%20%20%20%20%20%20%20%3CBottomSheetBackdrop%20%7B...props%7D%20appearsOnIndex%3D%7B0%7D%20disappearsOnIndex%3D%7B-1%7D%20%2F%3E%0A%20%20%20%20%20%20%3C%2F%3E%0A%20%20%20%20%29%2C%0A%20%20%20%20%5B%5D%2C%0A%20%20%29%3B%0A%20%20const%20scr%20%3D%20Dimensions.get%28%27screen%27%29%3B%0A%20%20const%20win%20%3D%20Dimensions.get%28%27window%27%29%3B%0A%20%20const%20gateOpen%20%3D%20%21%28probe.index%20%3C%3D%20-1%29%3B%0A%20%20return%20%28%0A%20%20%20%20%3CGestureHandlerRootView%20style%3D%7Bstyles.root%7D%3E%0A%20%20%20%20%20%20%3CView%20style%3D%7Bstyles.panel%7D%3E%0A%20%20%20%20%20%20%20%20%3CText%3Escreen%20%7Bscr.height%7D%20dp%20%2F%20window%20%7Bwin.height%7D%20dp%20%40%20scale%20%7Bscr.scale%7D%3C%2FText%3E%0A%20%20%20%20%20%20%20%20%3CText%3Eclosed%20sheet%20animatedIndex%20%3D%20%7BString%28probe.index%29%7D%3C%2FText%3E%0A%20%20%20%20%20%20%20%20%3CText%3EanimatedPosition%20%3D%20%7BString%28probe.position%29%7D%3C%2FText%3E%0A%20%20%20%20%20%20%20%20%3CText%20style%3D%7B%7B%20fontWeight%3A%20%27bold%27%20%7D%7D%3E%0A%20%20%20%20%20%20%20%20%20%20backdrop%20pointerEvents%20%3D%20%7BgateOpen%20%3F%20%22%27auto%27%20%20%28BUG%3A%20blocks%20every%20touch%29%22%20%3A%20%22%27none%27%20%20%28OK%29%22%7D%0A%20%20%20%20%20%20%20%20%3C%2FText%3E%0A%20%20%20%20%20%20%20%20%3CButton%20title%3D%7B%60RN%20Button%20tapped%20%24%7Btaps%7D%20times%60%7D%20onPress%3D%7B%28%29%20%3D%3E%20setTaps%28t%20%3D%3E%20t%20%2B%201%29%7D%20%2F%3E%0A%20%20%20%20%20%20%20%20%3CSwitch%20value%3D%7Bon%7D%20onValueChange%3D%7BsetOn%7D%20%2F%3E%0A%20%20%20%20%20%20%20%20%3CButton%20title%3D%22Open%20sheet%22%20onPress%3D%7B%28%29%20%3D%3E%20ref.current%3F.snapToIndex%280%29%7D%20%2F%3E%0A%20%20%20%20%20%20%3C%2FView%3E%0A%20%20%20%20%20%20%3CBottomSheet%0A%20%20%20%20%20%20%20%20ref%3D%7Bref%7D%0A%20%20%20%20%20%20%20%20index%3D%7B-1%7D%0A%20%20%20%20%20%20%20%20snapPoints%3D%7B%5B%2750%25%27%5D%7D%0A%20%20%20%20%20%20%20%20enablePanDownToClose%0A%20%20%20%20%20%20%20%20backdropComponent%3D%7BrenderBackdrop%7D%0A%20%20%20%20%20%20%3E%0A%20%20%20%20%20%20%20%20%3CBottomSheetView%20style%3D%7Bstyles.sheet%7D%3E%0A%20%20%20%20%20%20%20%20%20%20%3CText%3ESheet%20content%3C%2FText%3E%0A%20%20%20%20%20%20%20%20%3C%2FBottomSheetView%3E%0A%20%20%20%20%20%20%3C%2FBottomSheet%3E%0A%20%20%20%20%3C%2FGestureHandlerRootView%3E%0A%20%20%29%3B%0A%7D%0A%0Aconst%20styles%20%3D%20StyleSheet.create%28%7B%0A%20%20root%3A%20%7B%20flex%3A%201%20%7D%2C%0A%20%20panel%3A%20%7B%20flex%3A%201%2C%20justifyContent%3A%20%27center%27%2C%20padding%3A%2024%2C%20gap%3A%2012%20%7D%2C%0A%20%20sheet%3A%20%7B%20padding%3A%2024%20%7D%2C%0A%7D%29%3B%0A

Relevant log output

bash
# @gorhom/bottom-sheet 5.2.9, RN 0.86.2 (Fabric), reanimated 4.5.3, gesture-handler 2.32.0
# Samsung SM-S918B (Galaxy S23 Ultra), Android 16 / One UI, Display size => density 2.625
# env: window 411.4x891.4 @2.625, screen 411.4x891.4 @2.625

# right after mount (layout not calculated yet):
sheet "App Update" index=-1                   position=891.4285714285714 gorhomPointerEvents=none
# after layout — every mounted closed sheet:
sheet "Transits"   index=-0.9999999782638337  position=891.4285714285714 gorhomPointerEvents=auto
sheet "Colors"     index=-0.9999999782638337  position=891.4285714285714 gorhomPointerEvents=auto
sheet "Wisdom"     index=-0.9999999699037697  position=891.4285714285714 gorhomPointerEvents=auto
sheet "App Update" index=-0.9999999673957506  position=891.4285714285714 gorhomPointerEvents=auto

# a tap on a plain RN Pressable in a screen; JS target resolved by React Native, owner chain via fiber walk:
ROOT touchStart page=(288.1,847.9) target=262 owner=AnimatedComponent(View) < Wrap < AnimatedComponent(Wrap) < Wrap < GestureDetector < BottomSheetBackdropComponent < BottomSheetGestureHandlersProvider < BottomSheet < BottomSheet < AppBottomSheet < UpdateAvailableSheet

# same device at the default Display size (density 3, 780 dp): index=-1, pointerEvents=none, all touches work.

Source: gorhom/react-native-bottom-sheet