Skip to content

iOS: Sticky content lags/jumps when interactive keyboard dismissal gesture is started #1563

Open

Description

@chrispader

Describe the bug

When using the KeyboardGestureArea, in the beginning of interactive keyboard dismissal, when the user is starting to drag down the keyboard, the sticky content in KeyboardStickyView lags behind/stutters. After the user is continuing to pan the keyboard down (or up), the sticky content jumps to the new position.

Code snippet

Also reproducible without KeyboardChatScrollView

function MySimplifiedChatScreen() {
    const safeAreaPaddings = useSafeAreaPaddings();
    
    const [composerHeight, setComposerHeight] = useState<number>(CONST.CHAT_FOOTER_MIN_HEIGHT);
    const onComposerLayout = (e: LayoutChangeEvent) => setComposerHeight(e.nativeEvent.layout.height);

    const keyboardGestureAreaOffset = composerHeight - safeAreaPaddings.paddingBottom;
    
    return (
        <KeyboardGestureArea
            style={styles.flex1}
            offset={keyboardGestureAreaOffset}
            interpolator="ios"
            textInputNativeID={CONST.COMPOSER.NATIVE_ID}
            enableSwipeToDismiss={!isComposerFullSize}
        >
            {/* ... */}

            <FlashList
                renderScrollComponent={renderScrollComponent}
                data={dummyData}
                keyExtractor={(item) => item.id}
                renderItem={({item}) => <Text>{item.name}</Text>}
            />
            
            <KeyboardStickyView
                onLayout={onComposerLayout}
                offset={{closed: 0, opened: safeAreaPaddings.paddingBottom}}
            >
                <FooterWithInput />
            </KeyboardStickyView>

            {/* ... */}
        </KeyboardGestureArea>
    )
}

function renderScrollComponent({children, ...restProps}: ScrollViewProps) {
    const safeAreaPaddings = useSafeAreaPaddings();

    return (
        <KeyboardChatScrollView
            {...restProps}
            offset={safeAreaPaddings.paddingBottom}
            keyboardDismissMode="interactive"
        >
            {children}
        </KeyboardChatScrollView>
    );
}

Repo for reproducing

Reproducible in example app:

ScreenRecording_07-28-2026.13-56-52_1.mov

To Reproduce
Steps to reproduce the behavior:

  1. Open the app and login
  2. (Create and) open a chat
  3. Open the keyboard by tapping the input -> the keyboard opens, the footer is pushed up on top of the keyboard
  4. Start do dismiss the keyboard fast by dragging down the chat, while continuing to hold the finger on the screen.

Expected behavior

The sticky content (footer) stays "attached" to the keyboard and follows it's animation.

Actual behavior

The sticky content is still where the keyboard ended before the interactive keyboard dismissal gesture has started.

Screenshots
If applicable, add screenshots to help explain your problem.

Notice that on the second pan gesture, you can see the sticky content (footer) stuck for a couple of milliseconds.

ScreenRecording_07-21-2026.17-32-42_1.mov

Smartphone (please complete the following information):

  • Desktop OS: macOS 26
  • Device: iPhone 14 Pro/iOS simulator
  • OS: iOS 26.5.2/iOS 26.0
  • RN version: 0.85.2
  • RN architecture: new arch
  • JS engine: Hermes
  • Library version: 1.22.1

Additional context
Add any other context about the problem here.

Activity

  1. changed the title [-]iOS: Interactive keyboard dismissal lags/jumps when started[/-] [+]iOS: Sticky content lags/jumps when interactive keyboard dismissal gesture is started[/+] on Jul 21, 2026
  2. Deachronic commented on Oct 7, 2026

    @Deachronic

    We see the same thing on a physical iPhone 18 Pro Max (iOS 27.0.1), and it also reproduces without KeyboardStickyView / KeyboardGestureArea: just a view translated by useKeyboardAnimation().height above a list with keyboardDismissMode="interactive" (KeyboardProvider with preload={false}).

    const { height } = useKeyboardAnimation();
    // <FlatList inverted keyboardDismissMode="interactive" ... />
    // <Animated.View style={{ transform: [{ translateY: height }] }}><TextInput /></Animated.View>

    Measurements (60 fps screen recording, 3 drags, keyboard fully open beforehand). "Frozen" means the keyboard's top edge had moved and the input bar had not:

    Drag Frozen frames Keyboard travel while frozen Peak extra gap Then
    1 3 (50 ms) 78 pt 81 pt follows with the offset, snaps when the keyboard finishes hiding
    2 6 (100 ms) 24 pt 24 pt catches up in one frame
    3 12 (200 ms) 110 pt 110 pt follows 97–109 pt behind, snaps at the end

    The rest of the UI rendered every frame during these windows, so it isn't a JS or main-thread stall. On the iOS 26.5 Simulator a synthesized drag tracks from the first frame. We could only reproduce it on the device.

    Versions: observed on 1.21.9. We diffed the published 1.21.9 and 1.22.6 packages, and the iOS interactive path is byte-identical: KeyboardMovementObserver+Interactive.swift, +Watcher, +Animation, +Lifecycle, KeyboardMovementObserver.swift, KeyboardViewLocator.swift, KeyboardEventsIgnorer.swift and src/animated.tsx. So we expect 1.22.6 to behave the same, though we haven't run it on a device.

    Hypotheses (reasoned from source, not verified on the device):

    1. keyboardDidMoveInteractively returns early while !displayLink.isPaused, and on iOS useKeyboardAnimation only takes onKeyboardMoveStart and onKeyboardMoveInteractive. If the display link is still running when the drag begins, the first part of the drag would arrive only as onKeyboardMove and be dropped. That fits "lags, then jumps".
    2. With keyboardLayoutGuide tracking on iOS 26+, KeyboardTrackingView.interactive(point:) might return invalidPosition for the first KVO changes of a drag.

    A device log of the event names and heights for the first ~200 ms of a drag would tell these apart. We can try to collect one if that would help.

    Environment: iPhone 18 Pro Max, iOS 27.0.1 (Simulator: iOS 26.5) · react-native-keyboard-controller 1.21.9 · React Native 0.86.3 (New Architecture) · Expo SDK 57 · Reanimated 4.5.1

  3. Mingke commented on Oct 17, 2026

    @Mingke

    A related data point, about the end of the interactive dismissal rather than its start. It may share a cause.

    Setup

    • react-native-keyboard-controller 1.22.7, New Architecture, iPhone 16 Pro Max, iOS 27.
    • KeyboardGestureArea with textInputNativeID and offset = composerHeight + 11.
    • The composer is positioned from useReanimatedKeyboardAnimation().height.
    • The list uses keyboardDismissMode="interactive".

    What we measured (logging every change of height.value)

    • While the drag is in progress, the value falls smoothly, about 12 pt per frame, from -364 down to -offset (for example -112, -123, -128 with offset = 128).
    • It then goes from -offset to 0 in a single frame. The composer drops by offset + gap - restingBottom: about 97 pt with a two-line composer (offset 128), and about 24 pt with a one-line composer (offset 55).
    • With the keyboard shown, the raw UIKit frame is keyboard plus offset (364 + 128 = 492).

    Our reading of the source (please correct us)

    • On iOS 26+, the interactive value comes from the layout-guide tracking (KeyboardTrackingView.interactive(point:)). The invisible accessory view stays attached through the drag, so the value floors at the view's own height.
    • On resign, the swizzle calls KeyboardAreaExtender.hide(). That sets offset to 0 with no event and sets the ignore flag for a frame. keyboardWillHide then emits a single onKeyboardMoveStart(0), with no animation to inherit because the keyboard is already offscreen.
    • That matches the two-stage dismissal described in feat: keyboard offset on iOS #727.

    We are working around it in the app by mapping the interactive range [offset, keyboard] onto [0, keyboard]. An interactive tail emitted by the library would be the cleaner fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

KeyboardStickyView 🩹Anything related to KeyboardStickyView componentrepro providedIssue contains reproduction repository/code🍎 iOSiOS specific🐛 bugSomething isn't working👆 interactive keyboardAnything related to interactive keyboard dismissing

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions