React Native

React Native Keyboard Covering TextInput on iOS and Android

The on-screen keyboard covering a focused input field is a layout problem, not a keyboard problem — React Native doesn't automatically reposition your content when the keyboard appears, and the fix requires explicitly wrapping the relevant screen content in a component designed to shift or resize the layout when the keyboard shows.

The Problem

Tapping a TextInput near the bottom of the screen brings up the on-screen keyboard, which then visually covers the input itself (or nearby content the user needs to see while typing), with no automatic scrolling or repositioning happening.

Why It Happens

React Native leaves keyboard-avoidance behavior entirely up to the developer rather than handling it automatically, and the correct approach differs between iOS and Android in ways that are easy to get only partially right:

  • No KeyboardAvoidingView (or equivalent library) is used at all, so the layout simply stays fixed in place while the keyboard renders on top of it
  • KeyboardAvoidingView is used, but with the wrong behavior prop for the platform — iOS and Android generally need different values (padding typically works well on iOS, while Android often needs height or no explicit behavior at all, depending on the Android manifest's windowSoftInputMode setting)
  • The component is present but not correctly sized or positioned — commonly missing a flex: 1 on itself or its parent, causing it to not actually occupy the space it needs to be able to shift content within
  • The Android manifest's windowSoftInputMode is set to a value that conflicts with what KeyboardAvoidingView is also trying to control, causing inconsistent or doubled behavior specifically on Android

The Fix

Wrap the relevant screen content in KeyboardAvoidingView, with platform-specific behavior, since a single behavior value rarely works identically well on both platforms:

import { KeyboardAvoidingView, Platform } from 'react-native';

<KeyboardAvoidingView
  style={{ flex: 1 }}
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
  {/* screen content including TextInput */}
</KeyboardAvoidingView>

The flex: 1 on the component itself is important — without it, KeyboardAvoidingView may not actually occupy the full available space it needs to meaningfully shift or resize its content when the keyboard appears.

For screens where content might extend below the visible area once the keyboard is accounted for, combine KeyboardAvoidingView with a ScrollView, so users can scroll to reach content even when the keyboard takes up a significant portion of the screen:

<KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
  <ScrollView contentContainerStyle={{ flexGrow: 1 }}>
    {/* form content */}
  </ScrollView>
</KeyboardAvoidingView>

On Android specifically, check your app's manifest for a conflicting windowSoftInputMode setting, since Android's own native keyboard handling can interact with KeyboardAvoidingView in ways that produce inconsistent results if both are trying to manage the same behavior:

<!-- android/app/src/main/AndroidManifest.xml -->
<activity
  android:windowSoftInputMode="adjustResize"
  ...>

adjustResize is generally the setting that works most predictably alongside KeyboardAvoidingView's Android behavior — if this is set to adjustPan or left unset, you may see doubled shifting, insufficient shifting, or other inconsistent behavior specifically on Android even though the same code works correctly on iOS.

If you're using Expo, the relevant manifest setting is instead configured through app.json:

{
  "expo": {
    "android": {
      "softwareKeyboardLayoutMode": "resize"
    }
  }
}

Still Not Working?

If KeyboardAvoidingView is correctly configured but behavior still seems inconsistent, particularly with a header or tab bar present, add a keyboardVerticalOffset to account for any fixed-height elements above the KeyboardAvoidingView that it doesn't automatically know about — this is a common gap when the component is nested below a navigation header, since KeyboardAvoidingView only knows about its own immediate positioning, not any fixed UI elements rendered by a parent navigator:

<KeyboardAvoidingView
  style={{ flex: 1 }}
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
  keyboardVerticalOffset={Platform.OS === 'ios' ? 90 : 0}
>