skills/imfa-solutions/skills/rn-keyboard-controller

rn-keyboard-controller

SKILL.md

React Native Keyboard Controller — Best Practices Guide

Version 1.20.0 | react-native-reanimated required | Fabric & Paper supported

Table of Contents

Section Description
Critical Rules Non-negotiable rules for correct keyboard handling
Quick Decision Guide Which component/hook to use for your use case
Setup Installation, provider, platform config
Hooks Overview When and how to use each hook
Components Overview When and how to use each component
Performance 120fps animations, avoiding jank
Code Review Action Audit keyboard code against best practices
References Deep-dive files for APIs, patterns, troubleshooting

Critical Rules

  1. Wrap your app with <KeyboardProvider> — every hook and component depends on it. Place it above your navigation container.
  2. Always add "worklet" directive to every useKeyboardHandler and useFocusedInputHandler callback. Missing it causes silent failures or crashes.
  3. Never use useState to track keyboard height during animations. State updates trigger re-renders on the JS thread. Use useSharedValue from Reanimated instead — animations stay on the UI thread at 60-120fps.
  4. Use useKeyboardState with a selector to avoid unnecessary re-renders: useKeyboardState(s => s.isVisible) not useKeyboardState().
  5. Use KeyboardController.isVisible() in event handlers instead of reading from useKeyboardState — the static method doesn't cause re-renders.
  6. Set android:windowSoftInputMode="adjustResize" in AndroidManifest.xml (or equivalent in app.json). The library needs this on Android.
  7. Install react-native-reanimated — it's a mandatory peer dependency. The library will not work without it.
  8. Don't put <TextInput> inside <KeyboardExtender> — it won't work. Use KeyboardBackgroundView + KeyboardStickyView combo instead.

Quick Decision Guide

Pick the right tool for your use case:

Use Case Solution
Simple form with a few inputs KeyboardAvoidingView with behavior="padding"
Scrollable form with many inputs KeyboardAwareScrollView
Chat screen / messaging UI useKeyboardHandler + Animated.View for input bar
Sticky input bar that follows keyboard KeyboardStickyView
Form with prev/next/done navigation KeyboardToolbar
Interactive swipe-to-dismiss keyboard KeyboardGestureArea (Android 11+) + keyboardDismissMode="interactive" (iOS)
Content above keyboard (stickers, emoji) OverKeyboardView
Extend keyboard with quick actions KeyboardExtender
Match keyboard background color KeyboardBackgroundView
Custom keyboard-driven animation useKeyboardHandler (most powerful)
Check if keyboard is visible (no re-render) KeyboardController.isVisible()
Check if keyboard is visible (reactive) useKeyboardState(s => s.isVisible)
Dismiss keyboard programmatically KeyboardController.dismiss()
Navigate between inputs KeyboardController.setFocusTo("next"/"prev")

Setup

Installation

# Expo (recommended)
npx expo install react-native-keyboard-controller

# Yarn / NPM
yarn add react-native-keyboard-controller
# Also install reanimated if not present
yarn add react-native-reanimated

Provider Setup

import { KeyboardProvider } from "react-native-keyboard-controller";

export default function App() {
  return (
    <KeyboardProvider>
      {/* Navigation container and app content */}
    </KeyboardProvider>
  );
}

KeyboardProvider Props

Prop Type Default Purpose
statusBarTranslucent boolean true StatusBar translucency on Android
navigationBarTranslucent boolean false NavigationBar translucency on Android
preserveEdgeToEdge boolean false Keep edge-to-edge when module disabled
preload boolean true Preload keyboard to eliminate first-focus lag
enabled boolean true Initial enabled state

If the keyboard briefly flashes on app launch, disable preloading and call KeyboardController.preload() manually later.

Android Config

<!-- android/app/src/main/AndroidManifest.xml -->
<activity
  android:name=".MainActivity"
  android:windowSoftInputMode="adjustResize">
</activity>

For Expo managed, use expo-build-properties plugin if you need to set Kotlin version.

iOS Config

For 120fps on ProMotion displays, add to Info.plist:

<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>

Hooks Overview

useKeyboardHandler — The Power Hook

The most flexible API. Gives you frame-by-frame control over keyboard animations via worklet callbacks.

import { useKeyboardHandler } from "react-native-keyboard-controller";
import { useSharedValue } from "react-native-reanimated";

const height = useSharedValue(0);

useKeyboardHandler({
  onStart: (e) => { "worklet"; /* target values */ },
  onMove: (e) => { "worklet"; height.value = e.height; },
  onInteractive: (e) => { "worklet"; height.value = e.height; },
  onEnd: (e) => { "worklet"; /* final values */ },
}, []);
  • onInteractive fires during gesture-driven dismissal (Android 11+ with KeyboardGestureArea)
  • Event data: { height, progress, duration, target, timestamp, type, appearance }

useKeyboardAnimation / useReanimatedKeyboardAnimation

Simpler hooks that return animated height and progress values. Use when you just need to track keyboard position without lifecycle control.

// Reanimated version (recommended)
const { height, progress } = useReanimatedKeyboardAnimation();
// height: SharedValue (0 → keyboard height in px)
// progress: SharedValue (0 → 1)

useKeyboardState

Reactive state that triggers re-renders. Always use with a selector:

const isVisible = useKeyboardState(s => s.isVisible);
const appearance = useKeyboardState(s => s.appearance);

useFocusedInputHandler

Track text changes and selection in the currently focused input from the UI thread:

useFocusedInputHandler({
  onChangeText: ({ text }) => { "worklet"; /* ... */ },
  onSelectionChange: ({ selection }) => { "worklet"; /* ... */ },
}, []);

useReanimatedFocusedInput

Get layout information about the focused input as a SharedValue:

const { input } = useReanimatedFocusedInput();
// input.value.layout.absoluteY, input.value.target, etc.

useKeyboardController

Toggle the library on/off at runtime:

const { enabled, setEnabled } = useKeyboardController();

Full hook API details → read references/api-hooks.md

Components Overview

KeyboardAvoidingView

Drop-in replacement for RN's KeyboardAvoidingView. Consistent cross-platform behavior.

import { KeyboardAvoidingView } from "react-native-keyboard-controller";

<KeyboardAvoidingView behavior="padding" keyboardVerticalOffset={headerHeight}>
  <TextInput />
</KeyboardAvoidingView>

Behaviors: "padding" (most common), "height", "position", "translate-with-padding" (best for chat).

KeyboardAwareScrollView

Auto-scrolls to keep focused inputs visible. Works with FlatList, FlashList, SectionList.

import { KeyboardAwareScrollView } from "react-native-keyboard-controller";

<KeyboardAwareScrollView bottomOffset={20}>
  {/* Multiple TextInputs */}
</KeyboardAwareScrollView>

// With FlatList
<KeyboardAwareScrollView ScrollViewComponent={FlatList} data={data} renderItem={...} />

KeyboardStickyView

Translates a view to follow the keyboard without resizing the container.

import { KeyboardStickyView } from "react-native-keyboard-controller";

<KeyboardStickyView offset={{ closed: 0, opened: 20 }}>
  <InputBar />
</KeyboardStickyView>

KeyboardToolbar

Pre-built toolbar with prev/next/done navigation. Uses compound component pattern.

import { KeyboardToolbar } from "react-native-keyboard-controller";

<KeyboardToolbar />

// Or customized
<KeyboardToolbar>
  <KeyboardToolbar.Prev />
  <KeyboardToolbar.Content><Text>Custom</Text></KeyboardToolbar.Content>
  <KeyboardToolbar.Next />
  <KeyboardToolbar.Done text="Close" />
</KeyboardToolbar>

Full component API details → read references/api-components.md

Views Overview

View Purpose
KeyboardGestureArea Interactive keyboard dismissal via gestures (Android 11+)
OverKeyboardView Render content above keyboard without dismissing it
KeyboardExtender Extend keyboard with custom UI (no TextInput inside!)
KeyboardBackgroundView Match system keyboard background color

Full views & core API details → read references/api-views-core.md

Performance Rules

  1. All animation logic on the UI thread — use useSharedValue + useAnimatedStyle, never useState
  2. Add "worklet" to every handler in useKeyboardHandler and useFocusedInputHandler
  3. Use selectors with useKeyboardStateuseKeyboardState(s => s.isVisible) prevents full re-renders
  4. Use KeyboardController.isVisible() in press handlers instead of reactive state
  5. Memoize list items with React.memo when using keyboard-aware lists
  6. Enable 120fps on iOS via CADisableMinimumFrameDurationOnPhone in Info.plist
  7. Use useKeyboardHandler instead of KeyboardEvents when you need animated responses — events fire on JS thread, handlers run on UI thread

Gradual Animation Pattern

The recommended pattern for smooth keyboard tracking (especially in chat UIs):

const useGradualAnimation = () => {
  const height = useSharedValue(0);
  useKeyboardHandler({
    onMove: (e) => {
      "worklet";
      height.value = Math.max(e.height, 20); // minimum padding
    },
    onEnd: (e) => {
      "worklet";
      height.value = e.height;
    },
  }, []);
  return { height };
};

Code Review & Audit Action

When asked to review, audit, or fix keyboard handling code:

  1. Scan all files importing from react-native-keyboard-controller
  2. Check against these rules:
Check Severity What to look for
Missing KeyboardProvider CRITICAL App not wrapped in provider
Missing "worklet" directive CRITICAL useKeyboardHandler/useFocusedInputHandler callbacks without it
useState for keyboard height HIGH State updates during keyboard animation
useKeyboardState without selector HIGH Full object destructure causing extra re-renders
useKeyboardState in press handlers HIGH Should use KeyboardController.isVisible()
Missing adjustResize on Android HIGH windowSoftInputMode not set
TextInput inside KeyboardExtender HIGH Will not work — use KeyboardStickyView + KeyboardBackgroundView
Missing reanimated dependency HIGH Library requires react-native-reanimated
No 120fps iOS config MEDIUM Missing CADisableMinimumFrameDurationOnPhone
Using RN's KeyboardAvoidingView MEDIUM Should use library's version for cross-platform consistency
Missing keyboard preload handling LOW Keyboard may flash on app start
  1. Report findings in a table with file, line, issue, severity, and fix
  2. Fix all issues if the user confirms

Platform Differences

Behavior iOS Android
Frame-by-frame tracking Via layout animation scheduling Via WindowInsetsAnimationCallback
Interactive dismissal keyboardDismissMode="interactive" on ScrollView KeyboardGestureArea (API 30+)
Progress values Only 0 or 1 from events (use useKeyboardHandler for intermediate) Full intermediate values
Edge-to-edge N/A Library enables automatically
Soft input mode N/A adjustResize required, changeable at runtime

Migration

From RN's KeyboardAvoidingView

// Before — platform-specific behavior prop
import { KeyboardAvoidingView } from "react-native";
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : undefined}>

// After — consistent cross-platform
import { KeyboardAvoidingView } from "react-native-keyboard-controller";
<KeyboardAvoidingView behavior="padding">

From react-native-keyboard-aware-scroll-view

// Before
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
// After — same API, better behavior
import { KeyboardAwareScrollView } from "react-native-keyboard-controller";

Reference Files

For deep dives, read these files in references/:

File Contents
api-hooks.md Complete hooks API with all parameters, return types, edge cases
api-components.md Component props, behaviors, integration patterns
api-views-core.md Views (OverKeyboardView, KeyboardExtender, etc.) and core modules (KeyboardProvider, KeyboardController, KeyboardEvents)
patterns-and-examples.md Real-world patterns: chat, forms, navigation, interactive dismissal, custom hooks
troubleshooting.md Common errors, platform issues, compatibility matrix, Kotlin/Swift setup
Weekly Installs
2
GitHub Stars
1
First Seen
Mar 1, 2026
Installed on
amp2
cline2
openclaw2
opencode2
cursor2
kimi-cli2