<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Saul Zayn</title>
    <description>The latest articles on DEV Community by Saul Zayn (@zaynsaul).</description>
    <link>https://dev.to/zaynsaul</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F750507%2F4b8d02af-ad98-4aed-992d-b8b39911c6a0.jpeg</url>
      <title>DEV Community: Saul Zayn</title>
      <link>https://dev.to/zaynsaul</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zaynsaul"/>
    <language>en</language>
    <item>
      <title>Keyboard-aware forms in React Native and Expo, with a Save button that rides the keyboard</title>
      <dc:creator>Saul Zayn</dc:creator>
      <pubDate>Tue, 03 Nov 2026 23:50:39 +0000</pubDate>
      <link>https://dev.to/zaynsaul/keyboard-aware-forms-in-react-native-and-expo-with-a-save-button-that-rides-the-keyboard-250l</link>
      <guid>https://dev.to/zaynsaul/keyboard-aware-forms-in-react-native-and-expo-with-a-save-button-that-rides-the-keyboard-250l</guid>
      <description>&lt;p&gt;Every React Native app with a form hits the same wall. You tap a field low on the screen, the keyboard&lt;br&gt;
opens, and the field is gone behind it. The Save button too.&lt;/p&gt;

&lt;p&gt;The usual fixes get you most of the way. &lt;code&gt;KeyboardAvoidingView&lt;/code&gt; behaves differently on iOS and&lt;br&gt;
Android. &lt;code&gt;KeyboardAwareScrollView&lt;/code&gt; from&lt;br&gt;
&lt;a href="https://github.com/kirillzyusko/react-native-keyboard-controller" rel="noopener noreferrer"&gt;react-native-keyboard-controller&lt;/a&gt;&lt;br&gt;
is much better, and it's what I build on. But once a real form screen has a footer button, a&lt;br&gt;
previous / next toolbar, a modal sheet or a validation error, the edge cases start piling up.&lt;/p&gt;

&lt;p&gt;I hit all of them while building a booking app, so I pulled the fix out into a small package:&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/@saulzayn/rn-keyboard-form" rel="noopener noreferrer"&gt;&lt;code&gt;@saulzayn/rn-keyboard-form&lt;/code&gt;&lt;/a&gt;. This post shows&lt;br&gt;
the problems, what was behind each one, and how to use it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6mh0z6g265iit1js1zb1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6mh0z6g265iit1js1zb1.gif" alt="The same form without the package (the keyboard covers the field and Save) and with it" width="320" height="693"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What a form screen needs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;focused field&lt;/strong&gt; scrolls above the keyboard, and above anything sitting on the keyboard.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;footer&lt;/strong&gt; (Save, Continue, Send) stays at the bottom and &lt;strong&gt;rides on top of the keyboard&lt;/strong&gt; while
it's open, so you never have to close the keyboard to submit.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;toolbar&lt;/strong&gt; on the keyboard with ↑ / ↓ / Done to move between fields.&lt;/li&gt;
&lt;li&gt;The same thing &lt;strong&gt;inside an iOS modal sheet&lt;/strong&gt;, an &lt;strong&gt;iPad form sheet&lt;/strong&gt; and a &lt;strong&gt;bottom sheet&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Phones and tablets, portrait and landscape, and &lt;strong&gt;turning the device with the keyboard open&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is easy alone. Together they fight each other.&lt;/p&gt;
&lt;h2&gt;
  
  
  The problems I ran into
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The footer hides the field you're typing in.&lt;/strong&gt; If the Save button rides on the keyboard, the&lt;br&gt;
scroll view has to know the footer is there too, or it scrolls the field to just above the keyboard,&lt;br&gt;
right behind the button. So the footer's height has to be added to the scroll offset. And when the&lt;br&gt;
footer changes size (an error banner appears in it), that offset changes as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Changing that offset at the wrong moment scrolls twice.&lt;/strong&gt; Inside an Android &lt;code&gt;Modal&lt;/code&gt;, the footer's&lt;br&gt;
safe-area padding changes while it rides up with the keyboard. Updating the scroll offset during&lt;br&gt;
the animation made the field jump up twice. The fix was to hold the offset while the keyboard is&lt;br&gt;
opening or closing, and apply changes only once it has settled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Safe-area insets lie inside an iOS modal sheet.&lt;/strong&gt; &lt;code&gt;useSafeAreaInsets()&lt;/code&gt; still reports the&lt;br&gt;
home-bar inset inside a page sheet, where the real bottom inset is 0. The package measures it with a&lt;br&gt;
hidden &lt;code&gt;SafeAreaView&lt;/code&gt; instead, so the footer doesn't get a gap under it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. iPad sheets only move part of the way.&lt;/strong&gt; In an iPad form or page sheet, iOS lifts the sheet only&lt;br&gt;
part of the way when the keyboard opens. So "the keyboard is 300pt tall" isn't how much of your form&lt;br&gt;
it covers. The footer and toolbar are moved by the &lt;em&gt;overlap&lt;/em&gt;: how far the keyboard actually reaches&lt;br&gt;
into the form, measured in window coordinates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The rounded keyboard on iOS 26+.&lt;/strong&gt; The new keyboard has rounded top corners, and your form shows&lt;br&gt;
through around them. The toolbar has to lift a little, and a strip behind it has to reach under the&lt;br&gt;
corners.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Content that changes under the focused field.&lt;/strong&gt; An error appears above the field you're in, or a&lt;br&gt;
switch adds three more fields. The field moves, but nothing scrolls. The package re-measures the&lt;br&gt;
focused field when the content size changes and scrolls the rest of the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Jumping.&lt;/strong&gt; A Reddit commenter spotted this one in my demo video: moving to the next field while an&lt;br&gt;
error &lt;em&gt;and&lt;/em&gt; a footer banner appeared caused three scrolls in under a second. Now layout changes are&lt;br&gt;
checked within one or two frames and corrected without animation, so it reads as one scroll.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Bottom sheets.&lt;/strong&gt; A sheet that lifts itself above the keyboard plus a &lt;code&gt;KeyboardAwareScrollView&lt;/code&gt;&lt;br&gt;
inside it scrolled twice, once for each. Sheets get their own component that only scrolls the focused&lt;br&gt;
field into view once the sheet has moved. On Android a sheet is also its own window, so ↓ on the&lt;br&gt;
toolbar jumped to a field &lt;em&gt;behind&lt;/em&gt; the sheet until each form's fields were grouped.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;

&lt;p&gt;Install it next to its peers (with Expo, use &lt;code&gt;npx expo install&lt;/code&gt; for the last three):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @saulzayn/rn-keyboard-form react-native-keyboard-controller react-native-reanimated react-native-safe-area-context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;keyboard-controller is a native module, so you need a&lt;br&gt;
&lt;a href="https://docs.expo.dev/develop/development-builds/introduction/" rel="noopener noreferrer"&gt;development build&lt;/a&gt;, not Expo Go.&lt;/p&gt;
&lt;h3&gt;
  
  
  Wrap the app once
&lt;/h3&gt;

&lt;p&gt;With Expo Router, in the root &lt;code&gt;app/_layout.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SafeAreaProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-safe-area-context&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;KeyboardHost&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@saulzayn/rn-keyboard-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SafeAreaProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;KeyboardHost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;KeyboardHost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SafeAreaProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A form screen with a Save button
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TextInput&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SafeAreaView&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-safe-area-context&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FormScroll&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@saulzayn/rn-keyboard-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;EditProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FormScroll&lt;/span&gt;
      &lt;span class="na"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SafeAreaView&lt;/span&gt; &lt;span class="na"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Save"&lt;/span&gt; &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SafeAreaView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt; &lt;span class="na"&gt;keyboardType&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email-address"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Bio"&lt;/span&gt; &lt;span class="na"&gt;multiline&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;FormScroll&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it: the focused field stays above the keyboard and the Save button, Save rides on the&lt;br&gt;
keyboard, and the ↑ / ↓ / Done toolbar is on by default (&lt;code&gt;toolbar={false}&lt;/code&gt; turns it off).&lt;/p&gt;
&lt;h3&gt;
  
  
  A bottom sheet
&lt;/h3&gt;

&lt;p&gt;Your sheet moves itself above the keyboard; &lt;code&gt;SheetScroll&lt;/code&gt; scrolls the focused field into view inside&lt;br&gt;
it and keeps the footer in sight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SheetScroll&lt;/span&gt;
  &lt;span class="na"&gt;contentContainerStyle&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Send"&lt;/span&gt; &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Name"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Message"&lt;/span&gt; &lt;span class="na"&gt;multiline&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SheetScroll&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Forms in steps
&lt;/h3&gt;

&lt;p&gt;Give &lt;code&gt;FormScroll&lt;/code&gt; (or &lt;code&gt;SheetScroll&lt;/code&gt;) a &lt;code&gt;key&lt;/code&gt; per step and focus the next step's first field, and the&lt;br&gt;
keyboard stays up between steps on iOS. On Android, removing the focused field always closes the&lt;br&gt;
keyboard, so the example app waits for it to close and then opens it on the next field. The README&lt;br&gt;
has the details.&lt;/p&gt;
&lt;h3&gt;
  
  
  Theming
&lt;/h3&gt;

&lt;p&gt;Light and dark themes are built in, and you can change the toolbar colours, the Done label, spacing&lt;br&gt;
and the gap above the focused field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;KeyboardHost&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;mergeTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;darkTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;toolbar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#E5484D&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;doneLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it was tested
&lt;/h2&gt;

&lt;p&gt;Every screen in the example app on a Pixel 7 Pro, an iPhone with the iOS 26+ rounded keyboard, iPad&lt;br&gt;
and Android tablet, in portrait and landscape, turning the device with the keyboard open, in light&lt;br&gt;
and dark mode. It works with keyboard-controller 1.21.6 and newer (Expo SDK 56, 57 and 58).&lt;/p&gt;

&lt;p&gt;Two things are still on my list: a multiline field low on the screen first scrolls only to its caret&lt;br&gt;
(that's keyboard-controller's behaviour), and right after switching an Android app between full screen&lt;br&gt;
and a floating window, the first keyboard open can be off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/@saulzayn/rn-keyboard-form" rel="noopener noreferrer"&gt;@saulzayn/rn-keyboard-form&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub, with the example app: &lt;a href="https://github.com/ZaynSaul/rn-keyboard-form" rel="noopener noreferrer"&gt;ZaynSaul/rn-keyboard-form&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the heavy lifting is done by&lt;br&gt;
&lt;a href="https://github.com/kirillzyusko/react-native-keyboard-controller" rel="noopener noreferrer"&gt;react-native-keyboard-controller&lt;/a&gt;; this&lt;br&gt;
package is the form-screen layer on top. If your form breaks on a device or layout I haven't covered,&lt;br&gt;
please open an issue with a screen recording, and I'll fix it.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>javascript</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
