react-native-fieldflow
v1.3.0
Published
High-performance React Native keyboard avoidance and zero-config focus chaining. Now featuring Chat Mode for WhatsApp-style chat inputs and 120fps synchronized accessory views on both iOS and Android.
Downloads
532
Maintainers
Keywords
Readme
⭐ If this saves you time, consider starring the repo — it helps other developers find it.
📱 Demo
📦 Installation
npm install react-native-fieldflowRequirements: React Native ≥ 0.68 · Expo & bare RN supported · Zero native modules · No
pod install
⚡ Quick Start
Drop FieldForm and FieldInput into any screen. Focus chaining, keyboard avoidance, and return key types are all handled automatically.
import { FieldForm, FieldInput } from "react-native-fieldflow";
export default function SignUpScreen() {
return (
<FieldForm onSubmit={handleSubmit}>
<FieldInput placeholder="Full name" textContentType="name" />
<FieldInput
placeholder="Email"
textContentType="emailAddress"
keyboardType="email-address"
autoCapitalize="none"
/>
<FieldInput
placeholder="Phone"
textContentType="telephoneNumber"
keyboardType="phone-pad"
/>
<FieldInput
placeholder="Password"
textContentType="newPassword"
secureTextEntry
/>
<FieldInput
placeholder="Confirm password"
textContentType="newPassword"
secureTextEntry
/>
</FieldForm>
);
}What you get for free:
| | |
| ------------------------- | ------------------------------------------------------------------- |
| 🔗 Focus chaining | Fields 1–4 get returnKeyType="next", the last field gets "done" |
| ⌨️ Keyboard avoidance | Smooth animated layout shift — no jumps, no native modules |
| 🛠️ Accessory views | Cross-platform floating toolbars, perfectly synced with animations |
| 📜 Auto scroll | Focused field is always scrolled into view above the keyboard |
| 📱 Cross-platform | Identical behavior on iOS and Android, no Platform.OS switches |
🔄 Before & After
❌ Without FieldFlow — 5 refs · 5 wired handlers · platform switches · 40+ lines
const nameRef = useRef<TextInput>(null);
const emailRef = useRef<TextInput>(null);
const phoneRef = useRef<TextInput>(null);
const passRef = useRef<TextInput>(null);
const confirmRef = useRef<TextInput>(null);
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === "ios" ? 64 : 0}
style={{ flex: 1 }}
>
<ScrollView keyboardShouldPersistTaps="handled">
<TextInput
ref={nameRef}
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => emailRef.current?.focus()}
/>
<TextInput
ref={emailRef}
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => phoneRef.current?.focus()}
/>
<TextInput
ref={phoneRef}
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => passRef.current?.focus()}
/>
<TextInput
ref={passRef}
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => confirmRef.current?.focus()}
/>
<TextInput
ref={confirmRef}
returnKeyType="done"
onSubmitEditing={handleSubmit}
/>
</ScrollView>
</KeyboardAvoidingView>;✅ With FieldFlow — 0 refs · 0 handlers · 0 platform switches · 8 lines
import { FieldForm, FieldInput } from "react-native-fieldflow";
<FieldForm onSubmit={handleSubmit}>
<FieldInput placeholder="Full name" />
<FieldInput
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
/>
<FieldInput placeholder="Phone" keyboardType="phone-pad" />
<FieldInput placeholder="Password" secureTextEntry />
<FieldInput placeholder="Confirm" secureTextEntry />
</FieldForm>;🏗 How It Works
| Layer | What happens |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Keyboard tracking | Uses native keyboard listeners to track frame changes — zero native modules required |
| Spacer | An Animated.View at the bottom of the scroll content grows to match the keyboard frame, pushing content up in sync |
| Focus chain | Every FieldInput registers itself into an ordered list; tapping Next calls focus() on the next ref and scrolls it into view above the keyboard |
| Submit | The last field's Done button calls onSubmit and dismisses the keyboard |
Everything runs in JS — no native modules required. Works on Expo, bare RN, and New Architecture (Fabric).
📖 API Reference
<FieldForm>
The wrapper component that manages keyboard avoidance, scroll behavior, and the focus chain.
| Prop | Type | Default | Description |
| --------------------------- | -------------------------------- | ---------- | ------------------------------------------------------------------------------------------------- |
| onSubmit | () => void | — | Called when the last field's Done is tapped |
| extraScrollPadding | number | 140 | Gap (px) between the active field and the keyboard top edge |
| scrollable | boolean | true | Wrap children in a managed ScrollView |
| avoidKeyboard | boolean | true | Enable the animated keyboard spacer |
| keyboardAccessoryView | ReactNode | — | Toolbar that floats above the keyboard on both platforms |
| keyboardAccessoryViewMode | 'always' \| 'whenKeyboardOpen' | 'always' | always — always visible, lifts with keyboard · whenKeyboardOpen — hidden until keyboard opens |
| autoScroll | boolean | true | Scroll to focused field automatically |
| chainEnabled | boolean | true | Auto-focus next field on Next / Done |
| autoReturnKeyType | boolean | true | Auto-set returnKeyType to next / done |
| dismissKeyboardOnTap | boolean | false | Tap outside any input to dismiss the keyboard |
| submitOnLastFieldDone | boolean | false | Call onSubmit when Done is pressed on the final field |
| chatMode | boolean | false | High-performance mode for chat screens — bypasses padding and uses native scrollToEnd() |
| scrollViewProps | ScrollViewProps | — | Forwarded directly to the internal ScrollView |
| keyboardVerticalOffset | number \| (platform) => number | 0 / 25 | Static offset or per-platform resolver function |
| onKeyboardShow | (payload) => void | — | Fired when keyboard appears |
| onKeyboardHide | () => void | — | Fired when keyboard dismisses |
| autofocusFirst | boolean | false | Automatically focus the first field on mount (keyboard safe) |
| autofocusDelay | number | 500 | Delay in ms before autofocusing (waits for screen transitions) |
<FieldInput>
A drop-in replacement for TextInput. Accepts all standard TextInput props, plus:
| Prop | Type | Default | Description |
| ------------------ | ---------------------- | ------- | ---------------------------------------------------------------------------------------------- |
| skip | boolean | false | Exclude this field from the auto-focus chain |
| nextRef | RefObject<Focusable> | — | Override: focus a specific ref instead of the next detected field |
| onFormSubmit | () => void | — | Override: called when this is the last field and Done is tapped |
| isAccessoryField | boolean | false | Set to true if this input lives inside keyboardAccessoryView to bypass scroll measurements |
🎹 Keyboard Accessory View
A cross-platform floating toolbar, animated in sync with the keyboard on both iOS and Android.
<FieldForm
keyboardAccessoryView={
<View style={styles.toolbar}>
<TouchableOpacity onPress={Keyboard.dismiss}>
<Text>Done</Text>
</TouchableOpacity>
</View>
}
keyboardAccessoryViewMode="whenKeyboardOpen"
>
<FieldInput placeholder="Message..." />
</FieldForm>| Mode | Behavior |
| ---------------------- | ----------------------------------------------------------------------------------------- |
| 'always' (default) | Bar is always visible; slides up when the keyboard opens, back down when it closes |
| 'whenKeyboardOpen' | Bar is hidden when the keyboard is closed; fades in and slides up when the keyboard opens |
Animation details: Appearance uses an exponential ease-out curve so the bar settles exactly when the keyboard spring does. Dismissal in always mode uses a subtle spring bounce for a natural gravity-drop feel. Bar height is auto-measured and injected as paddingBottom on the ScrollView so the last field is always reachable.
🪝 Hooks
Two lightweight hooks for when you need keyboard state outside of FieldForm.
import { useKeyboardHeight, useKeyboardVisible } from "react-native-fieldflow";
const height = useKeyboardHeight(); // number — 0 when keyboard is hidden
const visible = useKeyboardVisible(); // booleanBoth hooks use keyboardWillShow / keyboardWillHide on iOS and keyboardDidShow / keyboardDidHide on Android. No polling, no timers.
useFieldFlowController
Exposes imperative actions to control the form from header buttons, tabs, or custom UI outside the field tree.
import { useFieldFlowController } from "react-native-fieldflow";
function HeaderSubmit() {
const { submit, focusFirst } = useFieldFlowController();
return <Button title="Submit" onPress={submit} />;
}| Method | Description |
| --------------- | ---------------------------------------------------------------------- |
| focusFirst() | Focuses the very first registered field in the form. |
| submit() | Triggers onSubmit and optionally dismisses the keyboard. |
| scrollTo(ref) | Manually scroll the form to keep a specific Focusable field visible. |
| dismiss() | Helper for Keyboard.dismiss(). |
📊 Comparison
🔌 Integrations
React Hook Form
<FieldForm onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="email"
render={({ field: { onChange, value, ref } }) => (
<FieldInput ref={ref} value={value} onChangeText={onChange} />
)}
/>
</FieldForm>Bottom Sheets & Custom ScrollViews
Pass a custom scroller (e.g., Gorhom's BottomSheetScrollView) to the ScrollViewComponent prop.
<FieldForm ScrollViewComponent={BottomSheetScrollView}>
<FieldInput placeholder="Automated chaining inside a bottom sheet!" />
</FieldForm>Modals & Offset
Adjust keyboardVerticalOffset for navigation headers or modal offsets.
<FieldForm keyboardVerticalOffset={(p) => p === 'ios' ? 44 : 0}>
<FieldInput />
</FieldForm>🧪 Example App
An Expo Router example app ships with 11 demo screens covering real-world patterns:
cd example
npx expo start| Screen | What it demonstrates |
| ----------------- | -------------------------------------------- |
| Login / Sign-up | Basic focus chaining |
| Checkout | Dynamic field skipping with nextRef |
| Chat | chatMode + keyboardAccessoryView toolbar |
| Long form | RefreshControl + auto-scroll |
| Collapsing header | Scroll-linked animated header |
| Hooks demo | useKeyboardHeight + useKeyboardVisible |
| React Navigation | Header offset handling |
❓ FAQ
Yes. FieldFlow measures the available window height, not screen height, so headers, tab bars, and custom chrome are accounted for automatically. No keyboardVerticalOffset guessing needed.
Wrap it with forwardRef and render FieldInput internally — it's picked up by the chain automatically.
const MyInput = forwardRef<TextInput, MyInputProps>((props, ref) => (
<View>
<Text>{props.label}</Text>
<FieldInput ref={ref} {...props} />
</View>
));Yes. Add skip={true} to any FieldInput. The field remains fully functional — it just doesn't participate in Next/Done handling.
Yes. Pass a nextRef to override the auto-detected next field.
const notesRef = useRef<TextInput>(null);
<FieldInput placeholder="Email" nextRef={notesRef} />
<FieldInput placeholder="Phone" skip />
<FieldInput placeholder="Notes" ref={notesRef} />Yes. FieldFlow uses Animated, Keyboard, and standard event listeners — all fully supported on both the old and new React Native architectures.
🤝 Contributing
Bug reports, feature requests, and pull requests are all welcome.
If you find an edge case — a device, navigation setup, or keyboard type that breaks the chain — please open an issue with a minimal reproduction.
MIT © Syed Sohaib
⭐ Star on GitHub — helps other developers discover this.
