skills/react-native-keyboard-handling/SKILL.md
Implement frame-perfect, native-feeling keyboard handling in React Native and Expo apps. Use when content is covered by the keyboard, when keyboard animations jank or "snap" on Android, when inputs are hidden behind the keyboard, when the keyboard covers fields on Android 15 / edge-to-edge even though older Android worked, or when building forms, sticky footers, or chat input bars that must track the keyboard. Triggers: "keyboard covers input", "KeyboardAvoidingView not working on Android", "keyboard animation janky", "keyboard sticky footer", "Android 15 keyboard", "edge-to-edge keyboard", react-native-keyboard-controller.
npx skillsauth add cenjie/skills react-native-keyboard-handlingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Make the keyboard feel native on both platforms by mapping two fundamentally different OS keyboard models onto one animated value that your UI binds to.
iOS and Android expose the keyboard in incompatible ways. Most "broken keyboard" bugs come from ignoring this asymmetry.
Anything that ignores this asymmetry will feel broken. The fix is to map both models onto a single shared animated value, then have every consumer (padded container, translated footer, scroll offset) bind to that one value and stop caring which platform feeds it.
| Situation | Use |
|---|---|
| Whole screen / single input should stay above keyboard | KeyboardAvoidingView (from react-native-keyboard-controller) |
| Scrollable form with multiple inputs | KeyboardAwareScrollView |
| One element pinned above the keyboard (footer, send bar, toolbar) | KeyboardStickyView |
| Fully custom animation tied to keyboard position | useReanimatedKeyboardAnimation() / useKeyboardHandler() + Reanimated SharedValue |
Default recommendation: use
react-native-keyboard-controller,
not React Native's built-in KeyboardAvoidingView. The built-in version relies
on LayoutAnimation and the late keyboardDidShow event, which has no per-frame
Android primitive — so Android content snaps instead of sliding and the timing
never matches the keyboard curve.
npm install react-native-keyboard-controller react-native-reanimated
KeyboardProvider. This installs the native
WindowInsetsAnimationCallback subscription on Android and handles
edge-to-edge automatically:
import { KeyboardProvider } from "react-native-keyboard-controller";
export default function App() {
return (
<KeyboardProvider>
{/* ...rest of the app */}
</KeyboardProvider>
);
}
Same API as RN's KeyboardAvoidingView; swapping the import alone fixes the
Android timing/snap problems.
import { KeyboardAvoidingView } from "react-native-keyboard-controller";
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
{/* input(s) */}
</KeyboardAvoidingView>
Auto-scrolls the minimum distance to keep the focused input (and caret/selection)
above the keyboard, using the same curve as the keyboard motion. bottomOffset
adds breathing room between the input and the keyboard top.
import { KeyboardAwareScrollView } from "react-native-keyboard-controller";
<KeyboardAwareScrollView bottomOffset={50} style={{ flex: 1 }}>
<TextInput placeholder="Field 1" />
<TextInput placeholder="Field 2" />
{/* more inputs */}
</KeyboardAwareScrollView>
Translates a single element instead of recomputing flex layout for the whole
screen. offset controls the gap when the keyboard is open vs closed.
import { KeyboardStickyView } from "react-native-keyboard-controller";
<KeyboardStickyView offset={{ closed: 0, opened: 20 }}>
<Footer />
</KeyboardStickyView>
For fully bespoke motion, read the keyboard height as a Reanimated SharedValue
and drive any style from it. Works identically on both platforms because the
library already merged the two OS models into this one value.
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
import Animated, { useAnimatedStyle } from "react-native-reanimated";
const { height } = useReanimatedKeyboardAnimation(); // SharedValue, per-frame
const style = useAnimatedStyle(() => ({
transform: [{ translateY: height.value }],
}));
<Animated.View style={style}>{/* ... */}</Animated.View>;
adjustResize / windowSoftInputMode contract
silently breaks — inputs get covered even though the exact same code worked on
Android 14. KeyboardProvider handles edge-to-edge for you; don't rely on
adjustResize.will* events never fire on Android. keyboardWillShow / keyboardWillHide
have no underlying Android primitive, and keyboardDidShow fires after the
animation finishes — too late to animate in sync. Don't build animations on
the raw Keyboard event listeners for Android.KeyboardAvoidingView + Android = snap, not slide. It uses
LayoutAnimation, whose timing doesn't match the keyboard curve on Android, so
motion looks janky. Prefer the keyboard-controller version.KeyboardAvoidingView for a multi-input
form won't track which input is focused, so the caret can stay hidden — use
KeyboardAwareScrollView. Using it to move a single footer forces an
unnecessary full-screen layout recompute — use KeyboardStickyView.Useful when debugging at the native layer or explaining the design.
NotificationCenter keyboard notifications carry
keyboardFrameEndUserInfoKey (final frame), keyboardAnimationDurationUserInfoKey
(timing), and keyboardAnimationCurveUserInfoKey (a private curve,
UIView.AnimationCurve(rawValue: 7)). Two snapshots, not frames.WindowInsetsAnimationCallback with four hooks —
onPrepare(), onStart(insets), onProgress(insets, runningAnimations) (fires
every frame), onEnd(). The library subscribes here and writes each frame's
inset into the shared value; on Android 11 it polyfills the per-frame stream
where native support is missing.Based on Margelo's The Go-To Guide for Understanding Keyboards in React Native (Part 1): https://blog.margelo.com/deep-dive-in-keyboard-handling
development
Launch both thermo-nuclear review subagents in parallel, then synthesize their findings. Use for thermos, double thermo review, or combined bug/security and code-quality branch audits.
development
Comprehensive security and correctness audit of a branch's changes. Use for thermo nuclear, thermonuclear, or deep review requests, or branch/PR diff audits focused on bugs, breaking changes, security issues, devex regressions, and feature-gate leaks.
development
Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. Use for a thermo-nuclear code quality review, thermonuclear review, deep code quality audit, or especially harsh maintainability review.
development
Use for custom storefronts requiring direct GraphQL queries/mutations for data fetching and cart operations. Choose this when you need full control over data fetching and rendering your own UI. NOT for Web Components - if the prompt mentions HTML tags like <shopify-store>, <shopify-cart>, use storefront-web-components instead.