[Bug]: keyboardBlurBehavior="restore" does not restore a dynamically sized BottomSheetModal after fillParent keyboard dismissal

Author: ParkJongJoon7128Created Sep 1, 2026Updated Sep 1, 2026
Labelsbug

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.8
  • react-native: 0.81.5
  • react-native-reanimated: 4.1.6
  • react-native-gesture-handler: 2.28.0
  • react-native-worklets: 0.5.1
  • react-native-keyboard-controller: 1.20.7
  • react: 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

Image

2. Sheet expanded while the keyboard is visible

Image

3. Keyboard hidden, but the sheet remains expanded

Image

4. Original height restored with the app-level workaround

Image

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:

typescript
  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:

  1. Preserve the active detent index before entering the temporary keyboard position.
  2. Clear the temporary-position state when the keyboard becomes hidden.
  3. Animate back to the preserved detent.
  4. Ensure that onChange emits a valid detent index rather than a pixel-like temporary value.

Related documentation and issues

Reproduction steps

  1. Run the following reproduction under Expo SDK 54 with React Native New Architecture enabled.
  2. Launch it on an iOS Simulator.
  3. Press Open bottom sheet.
  4. Focus the multiline input.
  5. Enter text so that the input remains actively focused.
  6. Press ⌘K in the Simulator to hide the software keyboard without focusing another field.
  7. Observe that the keyboard becomes hidden, but the sheet does not return to its original dynamic content height.

Minimal reproduction:

typescript
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

bash
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 0

Source: gorhom/react-native-bottom-sheet