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.
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.
Links
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