[Bug]: keyboardBlurBehavior="restore" does not restore a dynamically sized BottomSheetModal after fillParent keyboard dismissal
Version
v5
Reanimated Version
v3
Gesture Handler Version
v2
Platforms
iOS
What happened?
Summary
On iOS, a dynamically sized BottomSheetModal does not return to its original content height after the software keyboard is dismissed.
The reproduction uses:
- enableDynamicSizing
- No explicitly provided snapPoints
- keyboardBehavior="fillParent"
- keyboardBlurBehavior="restore"
- BottomSheetTextInput
- React Native New Architecture
- react-native-keyboard-controller's KeyboardProvider
When the input is focused, the sheet expands to fill the parent as expected. When the software keyboard is dismissed with ⌘K in the iOS Simulator while the input remains focused, the keyboard disappears, but the sheet remains at the temporary full-height position.
The original dynamically calculated content height is not restored, leaving a large blank area below the sheet content.
Expected behavior
After the keyboard becomes hidden, the sheet should return to the dynamic detent that was active before the keyboard appeared.
In this reproduction, that is the content-sized detent at index 0.
Actual behavior
The keyboard becomes hidden, but the sheet remains expanded close to the full-screen height.
The isolated reproduction screen reports:
- Before opening the keyboard: Sheet: index 0
- After hiding the keyboard: Keyboard: hidden
- After hiding the keyboard: Sheet: index 361.33331298828125
The last value is not a valid detent index. It appears that the temporary keyboard position is not normalized back to the previous dynamic detent.
This is an inference based on the callback value and the visible sheet position. I have not confirmed the internal root cause.
Environment
@gorhom/bottom-sheet:5.2.8react-native:0.81.5react-native-reanimated:4.1.6react-native-gesture-handler:2.28.0react-native-worklets:0.5.1react-native-keyboard-controller:1.20.7react:19.1.0- Expo SDK:
54.0.30 - New Architecture: Enabled
- Device: iPhone 17 Pro Simulator, Samsung Galaxy S9+
- OS: iOS 26.2
The issue form currently only provides Reanimated v1-v3 options. The actual version used to reproduce this issue is Reanimated
4.1.6.
Screenshots and recording
1. Initial dynamic content height
2. Sheet expanded while the keyboard is visible
3. Keyboard hidden, but the sheet remains expanded
4. Original height restored with the app-level workaround
Reproduction recording
https://github.com/user-attachments/assets/3d0fd333-6dda-45b7-9512-d3065d05a78e
Temporary app-level workaround
The issue can be worked around by listening for the platform keyboard-hide event and explicitly snapping the sheet back to the expected dynamic detent.
The following reusable hook is currently used in the application:
import { useEffect, type RefObject } from "react";
import { Keyboard, Platform } from "react-native";
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
interface UseBottomSheetKeyboardRestoreOptions {
bottomSheetRef: RefObject<BottomSheetModal | null>;
enabled?: boolean;
restoreIndex?: number;
}
export function useBottomSheetKeyboardRestore({
bottomSheetRef,
enabled = true,
restoreIndex = 0,
}: UseBottomSheetKeyboardRestoreOptions) {
useEffect(() => {
if (!enabled) {
return;
}
const eventName =
Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
const subscription = Keyboard.addListener(eventName, () => {
bottomSheetRef.current?.snapToIndex(restoreIndex);
});
return () => {
subscription.remove();
};
}, [bottomSheetRef, enabled, restoreIndex]);
}
Usage:
const bottomSheetRef = useRef<BottomSheetModal>(null);
const [isSheetOpen, setIsSheetOpen] = useState(false);
useBottomSheetKeyboardRestore({
bottomSheetRef,
enabled: isSheetOpen,
restoreIndex: 0,
});
return (
<BottomSheetModal
ref={bottomSheetRef}
enableDynamicSizing
keyboardBehavior="fillParent"
keyboardBlurBehavior="restore"
android_keyboardInputMode="adjustResize"
onChange={(index) => {
setIsSheetOpen(index >= 0);
}}
onDismiss={() => {
setIsSheetOpen(false);
}}
>
<BottomSheetView>
<BottomSheetTextInput multiline />
</BottomSheetView>
</BottomSheetModal>
);Why the workaround works
With dynamic sizing enabled and no provided snap points, the calculated content-height detent is index 0 in this reproduction.
The workaround explicitly requests that detent when the keyboard begins hiding on iOS. This prevents the sheet from remaining in the temporary fillParent position.
This should be treated as an app-level workaround rather than a general library fix:
It assumes that the intended restore detent is known.
Applications with multiple provided or dynamically reordered snap points may require a different restoreIndex.
A library-level fix should restore the internally saved pre-keyboard detent without requiring application code.
Suggested library behavior
When keyboardBlurBehavior="restore" is used, the library should:
- Preserve the active detent index before entering the temporary keyboard position.
- Clear the temporary-position state when the keyboard becomes hidden.
- Animate back to the preserved detent.
- Ensure that onChange emits a valid detent index rather than a pixel-like temporary value.
Related documentation and issues
Keyboard configuration props (https://gorhom.dev/react-native-bottom-sheet/props#keyboard-configuration)
Keyboard handling documentation (https://gorhom.dev/react-native-bottom-sheet/keyboard-handling)
Dynamic sizing documentation (https://gorhom.dev/react-native-bottom-sheet/dynamic-sizing)
Issue #2545: Incorrect restore behavior on iOS (https://github.com/gorhom/react-native-bottom-sheet/issues/2545)
Issue #2210: Sheet does not resize after the keyboard closes (https://github.com/gorhom/react-native-bottom-sheet/issues/2210)
Reproduction steps
- Run the following reproduction under Expo SDK 54 with React Native New Architecture enabled.
- Launch it on an iOS Simulator.
- Press Open bottom sheet.
- Focus the multiline input.
- Enter text so that the input remains actively focused.
- Press
⌘Kin the Simulator to hide the software keyboard without focusing another field. - Observe that the keyboard becomes hidden, but the sheet does not return to its original dynamic content height.
Minimal reproduction:
import React, { useRef, useState } from "react";
import {
Button,
StyleSheet,
useWindowDimensions,
View,
} from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { KeyboardProvider } from "react-native-keyboard-controller";
import {
BottomSheetModal,
BottomSheetModalProvider,
BottomSheetTextInput,
BottomSheetView,
} from "@gorhom/bottom-sheet";
const INITIAL_VALUE =
"Reproduction text keeps the multiline input active while the keyboard is dismissed.";
function ReproductionScreen() {
const bottomSheetRef = useRef<BottomSheetModal>(null);
const [value, setValue] = useState(INITIAL_VALUE);
const { height } = useWindowDimensions();
return (
<View style={styles.screen}>
<Button
title="Open bottom sheet"
onPress={() => bottomSheetRef.current?.present()}
/>
<BottomSheetModal
ref={bottomSheetRef}
enableDynamicSizing
maxDynamicContentSize={height - 60}
enablePanDownToClose
enableContentPanningGesture={false}
keyboardBehavior="fillParent"
keyboardBlurBehavior="restore"
android_keyboardInputMode="adjustResize"
enableBlurKeyboardOnGesture
onChange={(index, position, type) => {
console.log("BottomSheet onChange", {
index,
position,
type,
});
}}
>
<BottomSheetView style={styles.sheetContent}>
<BottomSheetTextInput
value={value}
onChangeText={setValue}
multiline
scrollEnabled
style={styles.input}
/>
<View style={styles.footer} />
</BottomSheetView>
</BottomSheetModal>
</View>
);
}
export default function App() {
return (
<GestureHandlerRootView style={styles.root}>
<KeyboardProvider>
<BottomSheetModalProvider>
<ReproductionScreen />
</BottomSheetModalProvider>
</KeyboardProvider>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
root: {
flex: 1,
},
screen: {
flex: 1,
justifyContent: "center",
padding: 24,
},
sheetContent: {
padding: 24,
gap: 20,
},
input: {
height: 120,
padding: 16,
borderRadius: 12,
backgroundColor: "#f4f5f7",
textAlignVertical: "top",
},
footer: {
height: 56,
borderRadius: 12,
backgroundColor: "#ff9900",
},
});Reproduction sample
https://snack.expo.dev/@gorhom/bottom-sheet---issue-reproduction-template
Relevant log output
No runtime exception or warning is emitted.
Observed state before opening the keyboard:
Keyboard: hidden
Sheet: index 0
Observed state after dismissing the keyboard without blurring the input:
Keyboard: hidden
Sheet: index 361.33331298828125
Sheet: index 0Source: gorhom/react-native-bottom-sheet