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-controller

On 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

OptionTypeDefaultDescription
enabledbooleantrueSet false to leave the element where it is.
offsetnumber16Gap kept between the element's bottom edge and the keyboard.

Returns

ValueTypeDescription
refAnimatedRef<View>Attach to the element that should stay visible.
onLayout(event) => voidAttach to the same element — it is how the resting position is measured.
animatedStyleAnimatedStyleApply 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.

On this page