On a notched phone, a React Native chat composer under a header is partly hidden by the keyboard or leaves a gap above it; how do you diagnose and fix it?
answer
- measure the error in points
- header height means offset
- inset-sized gap means double padding
- drop the bottom inset while typing
- Android 15+ fix landed in 0.86
basics
~20 sMeasure the error. Hidden by the header height means keyboardVerticalOffset is missing; a gap equal to the bottom safe-area inset means that inset padding stays while the keyboard is open. Android 15+ edge-to-edge problems before 0.86 need the upgrade.
solid answer
~40 sTreat it as up to three separate bugs and identify each by its size. If the composer is hidden by about the header's height, `KeyboardAvoidingView` is underestimating the overlap, because its frame is parent-relative while the keyboard reports screen coordinates; set `keyboardVerticalOffset` to the height above the view, taken from the navigator. If there is a gap equal to `insets.bottom`, the composer keeps its home-indicator padding while the keyboard, which already covers that area, is up; apply the inset only while the keyboard is hidden, tracked with `Keyboard` listeners. If Android 15+ devices with edge-to-edge misbehave on a version before 0.86, upgrade: 0.86 fixed `KeyboardAvoidingView` under edge-to-edge. Then remove hand-tuned offsets and retest both platforms and both Android navigation modes.
code
tsx · 43 linesimport {useEffect, useState} from 'react';
import type {ReactNode} from 'react';
import {Keyboard, KeyboardAvoidingView, Platform, TextInput, View} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
type ChatBodyProps = {headerHeight: number; children: ReactNode};
export function ChatBody({headerHeight, children}: ChatBodyProps) {
const insets = useSafeAreaInsets();
const [keyboardShown, setKeyboardShown] = useState(false);
useEffect(() => {
const show = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
() => setKeyboardShown(true),
);
const hide = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
() => setKeyboardShown(false),
);
return () => {
show.remove();
hide.remove();
};
}, []);
return (
<KeyboardAvoidingView
style={{flex: 1}}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={headerHeight}>
<View style={{flex: 1}}>{children}</View>
<View
style={{
paddingHorizontal: 12,
paddingTop: 8,
paddingBottom: 8 + (keyboardShown ? 0 : insets.bottom),
}}>
<TextInput placeholder="Message" multiline />
</View>
</KeyboardAvoidingView>
);
}go deeper
Recall that keyboardVerticalOffset accounts for a header above KeyboardAvoidingView and that the composer's bottom safe-area padding matters only while the keyboard is hidden.
Explain why the offset is needed (a parent-relative frame against screen coordinates) and why an inset-sized gap appears when bottom inset padding stays applied under the keyboard.
Diagnose by measuring the error, separate header, inset and edge-to-edge causes, upgrade to 0.86 for the Android 15+ fix, and remove device-tuned magic numbers.
Own a device and OS test matrix for input screens, covering notch, gesture and three-button navigation and Android 15+, and make keyboard-plus-inset layout a shared, tested component.
## The report A chat screen in a React Native app has a navigation header, a message list and a composer at the bottom. After the app moved to a target where Android draws edge to edge, and after it was tested on phones with a notch and a home indicator, three bugs arrive: 1. On iOS the composer is **partly hidden under the keyboard**, by about the height of the header. 2. On notched iPhones and gesture-navigation Android phones, there is a **gap** between the composer and the keyboard. 3. On some Android 15+ devices the keyboard handling is simply **wrong**. They look like one problem and are three. ## Bug 1: hidden by exactly the header's height `KeyboardAvoidingView` measures its frame with `onLayout`, **relative to its parent**, and compares it with the keyboard's `screenY` in **screen** coordinates. Under a header, the view's frame says `y = 0` while it really starts a header's height lower. The overlap is underestimated by that height, so the composer rises too little. - **Diagnosis:** the hidden amount matches the header height, and it disappears on a screen without a header. - **Fix:** set `keyboardVerticalOffset` to the full height above the view, meaning the header plus the status-bar area if the header covers it. Take the header height from your navigator, not from a hard-coded constant, because it differs by platform, device and orientation. ## Bug 2: a gap the size of the bottom inset With edge-to-edge on Android and the home indicator on iOS, the composer correctly has `paddingBottom: insets.bottom` so it clears the gesture area when the keyboard is **down**. When the keyboard opens: - the keyboard covers the gesture area anyway, and - `KeyboardAvoidingView` moves the composer up by the overlap measured to the view's bottom edge, which already sits inside that area, - so the composer's own bottom inset padding is still there, and shows as an empty strip above the keyboard. - **Diagnosis:** the gap equals `insets.bottom` and exists only on devices with a bottom inset. - **Fix:** apply the bottom inset only while the keyboard is hidden. Track visibility with `Keyboard` listeners (will-events on iOS, did-events on Android) and set `paddingBottom` to `keyboardShown ? base : base + insets.bottom`. ## Bug 3: Android 15+ edge-to-edge Android 15 forces edge-to-edge for apps targeting API 35, and Android 16 (React Native's default target since 0.81) removes the opt-out. Keyboard measurement assumptions that held with a resized window can break when the app draws behind the system bars. **React Native 0.86 fixed `KeyboardAvoidingView` on Android 15+ with edge-to-edge**, along with `measureInWindow` coordinates and `Dimensions` on older versions. - **Diagnosis:** the bug appears only on Android 15+ or with `edgeToEdgeEnabled`, and only on a version before 0.86. - **Fix:** upgrade to 0.86 or later (Expo SDK 57 ships 0.86) before layering workarounds. Remove any hand-tuned Android offsets afterwards, because they will now double-correct. ## A method that finds all three 1. **Measure the error.** Is it the header height, the bottom inset, or neither? The number points at the cause. 2. **Change one variable.** Hide the header, test a device without a notch, try an older Android version. 3. **Check both platforms and both navigation modes** on Android: gesture navigation and three-button navigation produce different bottom insets. 4. **Delete magic numbers.** Fixed offsets tuned for one device are the most common way these bugs come back. ## Symptom, cause, fix | Symptom | Size of the error | Cause | Fix | |---|---|---|---| | Composer partly under the keyboard | about the header height | parent-relative frame, no offset | `keyboardVerticalOffset` equal to the height above the view | | Strip between composer and keyboard | `insets.bottom` | inset padding kept while typing | apply the bottom inset only while the keyboard is hidden | | Wrong behaviour on Android 15+ only | varies | pre-0.86 edge-to-edge bug | upgrade to 0.86 or later and remove workarounds | | No movement at all | the full keyboard height | `behavior` not set | set `behavior` per platform | ## What the final screen looks like - `KeyboardAvoidingView` at the root of the screen, with `behavior` chosen per platform and `keyboardVerticalOffset` set to the header height. - The message list in a `flex: 1` container, with `keyboardShouldPersistTaps="handled"` if it contains tappable content. - The composer, padded by `insets.bottom` from `react-native-safe-area-context` only while the keyboard is hidden. - React Native 0.86 or later on Android.
- Why should keyboardVerticalOffset come from the navigator instead of a constant?The height above the view differs by platform, device, orientation and header configuration, for example large titles or a header that includes the status-bar area. A constant tuned on one phone underestimates or overestimates on another, bringing back the hidden-composer or gap bug. Read the real header height where the screen is rendered.
- After upgrading to React Native 0.86, the Android composer now sits too high. What happened?0.86 fixed `KeyboardAvoidingView` under Android 15+ edge-to-edge, so workarounds written for the old behaviour now double-correct, such as an extra Android-only offset or padding. Remove them and retest on Android 15+ and on an older version with `edgeToEdgeEnabled` set the way you ship.
saying these in an interview costs you the question
- Any keyboard gap is fixed by increasing keyboardVerticalOffset until it looks right.
- The bottom safe-area inset should stay applied while the keyboard is open.
- KeyboardAvoidingView accounts for the navigation header automatically.
- Android edge-to-edge problems are solved by wrapping the screen in core SafeAreaView.
- A fixed offset tuned on one device works across phones and orientations.