◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 27 days left. If your app calls it, check the migration target●SDK 58 — Moves to React Native 0.88. R8 is now on by default in Android release builds, and iOS prebuild generates a SceneDelegate●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 27 days left. If your app calls it, check the migration target●SDK 58 — Moves to React Native 0.88. R8 is now on by default in Android release builds, and iOS prebuild generates a SceneDelegate●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-04-15Intermediate

Fix Keyboard Hiding Input Fields in Rork Apps: A Complete Troubleshooting Guide

Solve the common issue of the on-screen keyboard overlapping text input fields in Rork apps. Learn KeyboardAvoidingView, ScrollView combinations, the useKeyboard hook, and SwiftUI keyboard avoidance with working code examples.

Rork611keyboardKeyboardAvoidingViewReact Native242Expo234troubleshooting79forms

Build a form screen, tap a text field, and suddenly the keyboard slides up — covering the very input the user is trying to fill out. If you've been building apps with Rork, you've almost certainly hit this. What makes it especially frustrating is that iOS and Android behave differently, so a fix that works on one platform quietly breaks the other.

This guide walks through the root cause, then gives you working code for each scenario you're likely to encounter.

Why This Happens

React Native (the framework underlying Rork) doesn't automatically scroll content upward when the software keyboard appears. Unlike a mobile browser, the framework won't naturally resize or shift the layout to keep the focused input visible — you have to wire that up yourself.

The behavior also differs between platforms. iOS expects the padding behavior prop, while Android relies on the native windowSoftInputMode setting, which you can control in app.json for Expo managed projects.

First Check: softwareKeyboardLayoutMode in app.json

{
  "expo": {
    "android": {
      "softwareKeyboardLayoutMode": "pan"
    }
  }
}

Setting this to "pan" makes the entire screen slide up on Android when the keyboard appears. "resize" shrinks the content area instead. For most form screens, "pan" feels more natural.

One important caveat: this setting is Android-only. iOS ignores it entirely, so you'll need a separate solution for Apple devices.

The Standard Fix: KeyboardAvoidingView

import {
  KeyboardAvoidingView,
  Platform,
  ScrollView,
  TextInput,
  StyleSheet,
} from 'react-native';
 
export default function ContactForm() {
  return (
    <KeyboardAvoidingView
      style={styles.container}
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 90 : 0}
    >
      <ScrollView
        contentContainerStyle={styles.scrollContent}
        keyboardShouldPersistTaps="handled"
      >
        <TextInput
          style={styles.input}
          placeholder="Name"
          returnKeyType="next"
        />
        <TextInput
          style={styles.input}
          placeholder="Email address"
          keyboardType="email-address"
          returnKeyType="next"
        />
        <TextInput
          style={styles.input}
          placeholder="Message"
          multiline
          numberOfLines={6}
          textAlignVertical="top"
        />
      </ScrollView>
    </KeyboardAvoidingView>
  );
}
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  scrollContent: {
    padding: 16,
    paddingBottom: 40,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 12,
    marginBottom: 16,
    fontSize: 16,
  },
});

Two things are critical here.

First, always branch the behavior prop with Platform.OS. iOS needs 'padding'; Android works better with 'height'. Leaving it undefined means iOS gets nothing.

Second, tune keyboardVerticalOffset to match your navigation bar height. With Expo Router's default stack header, 90 is usually close. If you're using a custom header, measure its actual height and adjust accordingly.

A Common Mistake: Forgetting the ScrollView

Adding KeyboardAvoidingView alone isn't enough if your form is tall. Without ScrollView inside it, inputs that scroll off the top of the screen are unreachable. Always pair them.

Also set keyboardShouldPersistTaps="handled" on the ScrollView. Without it, tapping outside a text field might not dismiss the keyboard, and taps on buttons inside the scroll area can get swallowed before they register.

For Chat-Style Layouts: useKeyboard Hook

When KeyboardAvoidingView doesn't behave (this happens frequently with bottom sheets, modals, and animated transitions), reading keyboard height directly gives you full control.

import { useEffect, useState } from 'react';
import { Keyboard, KeyboardEvent, Platform } from 'react-native';
 
export function useKeyboardHeight() {
  const [keyboardHeight, setKeyboardHeight] = useState(0);
 
  useEffect(() => {
    const showEvent =
      Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
    const hideEvent =
      Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
 
    const showSub = Keyboard.addListener(
      showEvent,
      (e: KeyboardEvent) => {
        setKeyboardHeight(e.endCoordinates.height);
      }
    );
    const hideSub = Keyboard.addListener(hideEvent, () => {
      setKeyboardHeight(0);
    });
 
    return () => {
      showSub.remove();
      hideSub.remove();
    };
  }, []);
 
  return keyboardHeight;
}
export default function ChatScreen() {
  const keyboardHeight = useKeyboardHeight();
 
  return (
    <View style={{ flex: 1 }}>
      <FlatList data={messages} /* ... */ />
      <View
        style={{
          paddingBottom: keyboardHeight > 0 ? keyboardHeight : 16,
          borderTopWidth: 1,
          borderTopColor: '#eee',
          padding: 8,
        }}
      >
        <TextInput placeholder="Type a message..." />
      </View>
    </View>
  );
}

This pattern works well for chat UIs where the input bar is pinned to the bottom. The re-render on keyboard height change is lightweight enough that it won't cause performance issues in a typical chat screen.

Rork Max (SwiftUI): Different Rules Apply

If you're generating a native SwiftUI app with Rork Max, the React Native approach doesn't apply. In SwiftUI, use .ignoresSafeArea(.keyboard) with a ScrollView.

struct ContactFormView: View {
    @State private var name = ""
    @State private var email = ""
    @State private var message = ""
 
    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                TextField("Name", text: $name)
                    .textFieldStyle(.roundedBorder)
                TextField("Email", text: $email)
                    .textFieldStyle(.roundedBorder)
                    .keyboardType(.emailAddress)
                TextEditor(text: $message)
                    .frame(height: 120)
                    .border(Color.gray.opacity(0.3))
            }
            .padding()
        }
        // Required when TextEditor is near the bottom of the screen
        .ignoresSafeArea(.keyboard, edges: .bottom)
    }
}

SwiftUI has built-in keyboard avoidance since iOS 15, but TextEditor specifically can still be obscured if the .ignoresSafeArea(.keyboard) modifier is missing. It's a small detail that's easy to overlook.

Tab Bar Screens Need Extra Offset

When your form is inside a tab-based navigation (Expo Router's Tabs, for example), add the tab bar height to keyboardVerticalOffset.

import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
function FormInsideTabs() {
  const insets = useSafeAreaInsets();
  const TAB_BAR_HEIGHT = 49; // Expo Router default
 
  return (
    <KeyboardAvoidingView
      style={{ flex: 1 }}
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={TAB_BAR_HEIGHT + insets.bottom}
    >
      {/* form content */}
    </KeyboardAvoidingView>
  );
}

The keyboardVerticalOffset tells the component where to anchor its calculations. Leaving out the tab bar height causes the layout to shift by the wrong amount — sometimes barely noticeable, sometimes wildly off.

Choosing the Right Approach

  • Simple forms (login, contact): KeyboardAvoidingView + ScrollView covers most cases.
  • Chat UI or fixed bottom input: Use the useKeyboardHeight hook for reliable control.
  • Rork Max (SwiftUI): Add .ignoresSafeArea(.keyboard) and let SwiftUI handle the rest.
  • Screens inside a tab navigator: Include tab bar height in keyboardVerticalOffset.

For related layout issues, see the Rork App Layout and Responsive Design Troubleshooting Guide. If you want to add input validation alongside these fixes, Form Validation with react-hook-form and Zod walks through that step by step.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-10-25
Sound Effects Stop at Play #40 — With expo-audio's createAudioPlayer, remove() Alone Isn't Cleanup
Creating a createAudioPlayer instance per play in expo-audio can leave Android silent after about 40 plays, with didJustFinish never arriving. What remove() and release() each do, three ways to restructure, and how to notice when it happens.
⬡ Dev Tools2026-10-07
Japanese Headings Won't Turn Bold on Android: How to Trace and Fix a fontWeight That Does Nothing
A troubleshooting note for Rork apps where headings look bold on iPhone but stay thin on Android. How to tell the symptoms apart, pick weights through fontFamily, and verify the fix.
⬡ Dev Tools2026-09-04
EAS secret visibility does not keep a value out of your app — deciding prefix and visibility separately
The EXPO_PUBLIC_ prefix decides what ships inside your app; EAS visibility decides who can read it. Why stacking them blanks a value on OTA updates, and how to check your build.