flutterdev.in › Packages › interactive_keyboard_dismiss

interactive_keyboard_dismiss

iOS-style interactive keyboard dismissal for Flutter: the keyboard follows your finger as a scroll view is dragged into it, on iOS and Android 11+.

v0.1.1160/160 pub pointsMITAndroidiOS
interactive_keyboard_dismiss: ^0.1.1

Drag a Flutter scroll view down into the software keyboard and the keyboard follows your finger. Let go and it either closes or springs back, like iOS UIScrollView.keyboardDismissMode = .interactive. Works on iOS and Android 11+.

It solves flutter/flutter#57609 ("Support for interactive keyboard dismissal") and its Android counterpart flutter/flutter#62876. The only earlier package, flutter_interactive_keyboard, has not been updated since 2021.

The iOS keyboard following the finger as the chat list is dragged down, then dismissing

Recorded on the iOS simulator from example/: the keyboard tracks the finger down, back up, and closes when the drag is carried through.

Install

dependencies:
  interactive_keyboard_dismiss: ^0.1.1

Android: keep the default android:windowSoftInputMode="adjustResize" on your activity. That is what Flutter's template uses, and the plugin needs it to control the IME inset.

Usage

Wrap the part of the screen that holds both the scroll view and the input bar:

import 'package:interactive_keyboard_dismiss/interactive_keyboard_dismiss.dart';

@override
Widget build(BuildContext context) {
  return InteractiveKeyboardDismiss(
    child: Scaffold(
      appBar: AppBar(title: const Text('Chat')),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              reverse: true,
              physics: const AlwaysScrollableScrollPhysics(),
              itemCount: messages.length,
              itemBuilder: (context, i) => MessageBubble(messages[i]),
            ),
          ),
          const ChatInputBar(),
        ],
      ),
    ),
  );
}

While a drag is running, InteractiveKeyboardDismiss passes the visible keyboard height down as MediaQuery.viewInsets.bottom. The Scaffold in the example therefore resizes with the keyboard and the input bar stays on its top edge.

If you place the widget inside a Scaffold body, or you want to handle the inset yourself, turn that off and read the value directly:

Scaffold(
  resizeToAvoidBottomInset: false,
  body: InteractiveKeyboardDismiss(
    adjustMediaQuery: false,
    child: Column(
      children: [
        Expanded(child: messageList),
        // Pads by the live, finger-tracked keyboard height.
        const InteractiveKeyboardPadding(child: ChatInputBar()),
      ],
    ),
  ),
);

InteractiveKeyboardInset.of(context) and InteractiveKeyboardInsetBuilder return the same value. Don't combine the padding helper with a Scaffold that already resizes, or the inset is applied twice.

Options

Parameter Default Meaning
enabled true Turn tracking off without rebuilding the child.
controller own InteractiveKeyboardDismissController: keyboardOffset, keyboardInset, keyboardHeight, phase, mode.
thresholds 300 px/s, 0.5 Android only. A fast downward release dismisses, a fast upward one restores, and a slow one is decided by how far the keyboard was pulled.
requireScrollGesture true Android only. Track only drags that a Scrollable accepted.
unfocusOnDismiss true Unfocus on dismiss. If false, the keyboard is hidden with TextInput.hide and focus is kept (Android only).
adjustMediaQuery true Rewrite MediaQuery.viewInsets.bottom for the child while a drag runs.
onDismissed / onRestored – Called when the keyboard has been dismissed or put back.

decideKeyboardRelease and keyboardOffsetForPointer are public pure functions, so you can unit-test your own thresholds. InteractiveKeyboardChannel.instance.diagnostics() returns what the native side sees. Attach it to bug reports.

How it works

iOS. From iOS 26 the keyboard is drawn outside the app's view hierarchy. Moving the keyboard's host view has no effect on screen (the UIInputSetHostView technique), and neither does the engine's screenshot hook. Both were tried and measured on the iOS 26 simulator. What still works is UIKit's own behaviour. The plugin keeps a hidden UIScrollView with keyboardDismissMode = .interactive inside the FlutterView and moves its pan gesture recognizer onto the FlutterView. The recognizer:

UIKit then moves the real keyboard with the finger and decides between dismissing and restoring. The plugin reports that decision to Dart, which keeps the layout in step.

Android (API 30+). The plugin uses WindowInsetsControllerCompat.controlWindowInsetsAnimation(ime), and every finger move calls setInsetsAndAlpha. Flutter's engine forwards the moving inset to viewInsets, so the layout follows in the same frame. On release, the Dart thresholds pick the outcome. The native side and the Dart offset then run the same critically damped spring and finish the controller either shown or hidden.

Platform support

Platform Behaviour
iOS 13+ Interactive, driven by UIKit. Verified on the iOS 18.6 and 26.0 simulators with real touch input.
Android 11+ (API 30+) Interactive (WindowInsetsAnimationController). Verified on an API 36 emulator with real touch input.
Android 7–10 (API 24–29) Dismisses as soon as the drag enters the keyboard.
Web, macOS, Windows, Linux Pass-through: the child is rendered unchanged.

Limitations

License

MIT © 2026 Manish Kumar Panday

More Flutter packages