useKeyboardAvoidance
Lift an element just clear of the software keyboard.
Measures an element and translates it by exactly the amount the keyboard overlaps it — and not at all when there is no overlap.
This is the difference from KeyboardAvoidingView, which shifts or pads an
entire subtree by the full keyboard height regardless of where the element
sits. A field near the top of the screen gets pushed off it; a field at the
bottom of a long form still ends up half covered.
The translation runs on the UI thread, so the element tracks the keyboard's own interpolation frame for frame and nothing re-renders while it moves.
Install the keyboard controller
npx expo install react-native-keyboard-controllerOn Android this is close to required. Without it the hook falls back to
Reanimated's useAnimatedKeyboard, which is deprecated in Reanimated 4
and, merely by being called, switches Android out of adjustResize into
manual inset handling for the whole app. That is a global side effect from
a local hook, and it is the usual reason keyboard avoidance works on iOS and
breaks on Android.
PanelUIProvider mounts the controller's KeyboardProvider for you when the
package is installed, so there is no extra setup. If you see
Couldn't find real values for KeyboardContext, something is rendering above
PanelUIProvider — move it below.
Usage
Most of the time you do not need this hook directly — Input takes an
avoidKeyboard prop, and KeyboardAvoider wraps arbitrary content:
import { Input, KeyboardAvoider } from 'panelui-native';
<Input avoidKeyboard label="Comment" placeholder="Say something…" />;
<KeyboardAvoider offset={24} className="gap-3 p-4">
<Input label="Message" />
<Button fullWidth>Send</Button>
</KeyboardAvoider>;Reach for the hook when you want to move something other than the element being measured, or when you are building your own component:
import Animated from 'react-native-reanimated';
import { useKeyboardAvoidance } from 'panelui-native';
function Composer() {
const { ref, onLayout, animatedStyle } = useKeyboardAvoidance({ offset: 12 });
return (
<Animated.View
ref={ref}
onLayout={onLayout}
style={animatedStyle}
className="flex-row items-end gap-2 border-t border-border p-3"
>
<Input containerClassName="flex-1" placeholder="Message" />
<Button size="icon" onPress={send}>
<SendIcon size={18} />
</Button>
</Animated.View>
);
}Disable it conditionally rather than calling the hook conditionally:
const { ref, onLayout, animatedStyle } = useKeyboardAvoidance({
enabled: !isModalOpen,
});API Reference
Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Set false to leave the element where it is. |
offset | number | 16 | Gap kept between the element's bottom edge and the keyboard. |
Returns
| Value | Type | Description |
|---|---|---|
ref | AnimatedRef<View> | Attach to the element that should stay visible. |
onLayout | (event) => void | Attach to the same element — it is how the resting position is measured. |
animatedStyle | AnimatedStyle | Apply to the same element. |
All three go on the same element. The onLayout handler is not optional: it is
what records where the element sits with no translation applied, and the hook
deliberately ignores layout events fired while the element is already lifted so
the position cannot creep upwards.
Notes
The element must be an Animated component — Animated.View, or anything from
Animated.createAnimatedComponent. A plain View will not accept the animated
style.
Inside a ScrollView, prefer the scroll view's own
automaticallyAdjustKeyboardInsets (iOS) or a keyboard-aware scroll container.
This hook moves a single element and does not know about scroll offset.
Why not enabled: false on every field
Input deliberately does not call this hook with enabled: false when
avoidKeyboard is unset — it renders a different container component instead.
Calling the underlying keyboard hook at all has app-wide consequences on the
fallback path, and a field that never asked to avoid the keyboard must not
impose them on every other screen. Do the same in your own components: keep the
hook behind a component boundary rather than a flag.