Mobile | Wed May 14 20254 views

No More Overlapping Keyboards in RN

Hi there! 👋

In this tutorial, I'll demonstrate how to implement a smooth keyboard-avoiding solution using react-native-keyboard-controller with Reanimated. This powerful combination ensures your UI elements gracefully adapt to keyboard appearance, creating a polished user experience without any overlapping issues.

🧙‍♂️ Want to become a React Native master? Check out the React Native Course.

Also, dive into the React with TypeScript Course to master TypeScript, handle payments, and deploy scalable apps. Learn more

How to use

First, install the dependencies:

npx expo install react-native-keyboard-controller react-native-reanimated

Will create a hook called useGradualAnimation, that will calculate the height of the keyboard every frame using the useKeyboardHandler hook from react-native-keyboard-controller and apply it to the view using the useSharedValue hook from react-native-reanimated.

You can play with the OFFSET variable to see how it works for your own use case.

import { useKeyboardHandler } from "react-native-keyboard-controller";
import { useSharedValue } from "react-native-reanimated";
 
const OFFSET = 42;
 
export const useGradualAnimation = () => {
  const totalOffset = OFFSET;
 
  const height = useSharedValue(totalOffset);
 
  useKeyboardHandler(
    {
      onMove: (e) => {
        "worklet";
        height.value =
          e.height > 0 ? Math.max(e.height + OFFSET, totalOffset) : totalOffset;
      },
    },
    [],
  );
  return { height };
};

After that, you can implement the keyboard avoiding view:

import React, { useEffect, useRef } from "react";
import { useGradualAnimation } from "@/hooks/useGradualAnimation";
import {
  Button,
  Keyboard,
  StyleSheet,
  Text,
  TextInput,
  useColorScheme,
  View,
} from "react-native";
import { KeyboardToolbar } from "react-native-keyboard-controller";
import Animated, { useAnimatedStyle } from "react-native-reanimated";
import { Stack } from "expo-router";
import { Colors } from "@/constants/Colors";
 
export default function Schedule() {
  const { height } = useGradualAnimation();
  const colorScheme = useColorScheme();
  const isDark = colorScheme === "dark";
  const inputRef = useRef<TextInput>(null);
 
  const keyboardPadding = useAnimatedStyle(() => {
    return {
      height: height.value,
    };
  }, []);
 
  const toggleKeyboard = () => {
    if (Keyboard.isVisible()) {
      Keyboard.dismiss();
    } else {
      inputRef.current?.focus();
    }
  };
 
  return (
    <>
      <Stack.Screen
        options={{
          headerRight: () => (
            <Button title="Toggle keyboard" onPress={toggleKeyboard} />
          ),
        }}
      />
      <View style={{ flex: 1, padding: 16 }}>
        <TextInput
          ref={inputRef}
          placeholder="Hello this is an input"
          multiline
          autoFocus
          numberOfLines={8}
          maxLength={280}
          style={{
            flex: 1,
            padding: 16,
            fontSize: 16,
            color: isDark ? Colors.dark.text : Colors.light.text,
            borderWidth: StyleSheet.hairlineWidth,
            borderColor: "black",
            borderRadius: 16,
            marginBottom: 16,
          }}
          textAlignVertical="top"
        />
 
        <View
          style={{
            flexDirection: "row",
            justifyContent: "space-between",
            gap: 16,
          }}
        >
          {/* Footer or something */}
          <Item name="Item 1" />
          <Item name="Item 2" />
          <Item name="Item 3" />
        </View>
 
        <Animated.View style={keyboardPadding} />
      </View>
 
      <KeyboardToolbar
        content={<Text>This is a toolbar</Text>}
        showArrows={false}
        insets={{ left: 16, right: 0 }}
        doneText="Close keyboard"
      />
    </>
  );
}
 
function Item({ name }: { name: string }) {
  return (
    <View
      style={{
        height: 100,
        flexGrow: 1,
        alignItems: "center",
        justifyContent: "center",
        borderWidth: StyleSheet.hairlineWidth,
        borderColor: "black",
        borderRadius: 16,
      }}
    >
      <Text>{name}</Text>
    </View>
  );
}

And that's it! You now have a keyboard avoiding view that smoothly adapts to keyboard appearance and dismissal, working flawlessly on both Android and iOS platforms. This implementation ensures your UI remains accessible even when the keyboard is visible, providing a seamless user experience.

Demo

Watch the video for a detailed walkthrough of this implementation, including additional tips and customization options to fit your specific project needs.

Youtube GitHub

Stay tuned! 🔌

Create an account to get our newsletter with updates, tips, and exclusive content — delivered straight to your inbox. Just hit the Sign Up button in the top right to get started.

Go back to Projects