フォームとキーボード

モバイルのフォームはキーボード制御が大半の難易度を占める。 入力欄が隠れない、Tab/Next で次に飛べる、安全に閉じられる、ところまで設計できれば一人前。

<TextInput> の主要 props

prop意味
value / onChangeText制御コンポーネント。onChange はイベントオブジェクトを返すので普通は onChangeText
placeholderプレースホルダ
placeholderTextColorプレースホルダの色
keyboardType"default" / "email-address" / "numeric" / "phone-pad" / "decimal-pad" / "url"
autoCapitalize"none" / "sentences" / "words" / "characters"
autoCorrect自動修正(メール等で off に)
autoComplete"email" / "password" / "name" 等。OS の入力候補が出る
textContentType (iOS)iOS 専用ヒント。"emailAddress" / "oneTimeCode" など
secureTextEntryパスワード(マスク)
multiline複数行
numberOfLinesmultiline 時の表示行数
maxLength最大文字数
returnKeyType"done" / "next" / "send" / "go"
onSubmitEditingキーボードの完了押下
blurOnSubmit送信時にキーボード閉じる(multiline は既定で false
selectTextOnFocusフォーカス時に全選択
editable編集可否

制御コンポーネント基本

import { TextInput } from "react-native"
import { useState } from "react"

function EmailInput() {
  const [email, setEmail] = useState("")
  return (
    <TextInput
      value={email}
      onChangeText={setEmail}
      placeholder="メールアドレス"
      keyboardType="email-address"
      autoCapitalize="none"
      autoCorrect={false}
      autoComplete="email"
      textContentType="emailAddress"
      style={{ borderWidth: 1, padding: 10, borderRadius: 8 }}
    />
  )
}

キーボードを閉じる

外をタップで閉じる

import { TouchableWithoutFeedback, Keyboard, View } from "react-native"

<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
  <View style={{ flex: 1 }}>
    {/* 中身 */}
  </View>
</TouchableWithoutFeedback>

プログラム的に閉じる

import { Keyboard } from "react-native"

Keyboard.dismiss()

キーボードで隠れないようにする

<KeyboardAvoidingView>(標準)

フォームを囲うとキーボード分だけ画面が押し上げられる。iOS は "padding"、Android は "height" または undefinedがよく使われる。

import { KeyboardAvoidingView, Platform } from "react-native"

<KeyboardAvoidingView
  style={{ flex: 1 }}
  behavior={Platform.OS === "ios" ? "padding" : undefined}
  keyboardVerticalOffset={Platform.OS === "ios" ? 64 : 0}
>
  <ScrollView>
    <TextInput ... />
  </ScrollView>
</KeyboardAvoidingView>

react-native-keyboard-controller(推奨)

標準の KeyboardAvoidingView挙動がプラットフォームで揺れる。 2026 現在は react-native-keyboard-controller がより滑らかで定番化している。

npx expo install react-native-keyboard-controller
import { KeyboardAvoidingView, KeyboardProvider } from "react-native-keyboard-controller"

// アプリのルートで包む
<KeyboardProvider>
  <Slot />
</KeyboardProvider>

// 各画面で
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
  ...
</KeyboardAvoidingView>

Next/Done で次に飛ぶ

returnKeyType + onSubmitEditingキーボードの「次へ」ボタンを活用。

import { useRef } from "react"

const passwordRef = useRef<TextInput>(null)

<TextInput
  placeholder="メール"
  returnKeyType="next"
  onSubmitEditing={() => passwordRef.current?.focus()}
  blurOnSubmit={false}
/>
<TextInput
  ref={passwordRef}
  placeholder="パスワード"
  secureTextEntry
  returnKeyType="done"
  onSubmitEditing={submit}
/>

パスワードと OTP

パスワード入力 + 表示切替

const [show, setShow] = useState(false)

<View style={{ flexDirection: "row" }}>
  <TextInput
    secureTextEntry={!show}
    autoComplete="password"
    textContentType="password"
    style={{ flex: 1 }}
  />
  <Pressable onPress={() => setShow(s => !s)}>
    <Text>{show ? "🙈" : "👁"}</Text>
  </Pressable>
</View>

SMS の OTP 自動入力(iOS)

<TextInput
  textContentType="oneTimeCode"
  autoComplete="sms-otp"
  keyboardType="number-pad"
/>

iOS は SMS が来たらキーボード上に「コードを入力」が出てワンタップで入る。Android は autoComplete="sms-otp" で自動入力。

バリデーション

シンプル: useState で手書き

const [email, setEmail] = useState("")
const [error, setError] = useState<string | null>(null)

const validate = () => {
  if (!email.includes("@")) { setError("メールが不正"); return false }
  setError(null); return true
}

<TextInput value={email} onChangeText={setEmail} />
{error && <Text style={{ color: "red" }}>{error}</Text>}

本格的: react-hook-form + zod

Web と同じく RN で使える定番の組み合わせ。

npm install react-hook-form zod @hookform/resolvers
import { useForm, Controller } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"

const Schema = z.object({
  email: z.string().email("メールが不正"),
  password: z.string().min(8, "8文字以上"),
})
type Form = z.infer<typeof Schema>

export default function Login() {
  const { control, handleSubmit, formState: { errors } } = useForm<Form>({
    resolver: zodResolver(Schema),
  })

  const onSubmit = (data: Form) => signIn(data)

  return (
    <>
      <Controller
        control={control}
        name="email"
        render={({ field: { value, onChange, onBlur } }) => (
          <TextInput
            value={value} onChangeText={onChange} onBlur={onBlur}
            placeholder="メール"
          />
        )}
      />
      {errors.email && <Text style={{ color: "red" }}>{errors.email.message}</Text>}

      <Controller
        control={control}
        name="password"
        render={({ field: { value, onChange, onBlur } }) => (
          <TextInput
            value={value} onChangeText={onChange} onBlur={onBlur}
            secureTextEntry placeholder="パスワード"
          />
        )}
      />
      {errors.password && <Text style={{ color: "red" }}>{errors.password.message}</Text>}

      <Pressable onPress={handleSubmit(onSubmit)}><Text>送信</Text></Pressable>
    </>
  )
}

選択系

Picker

標準の <Picker> は廃止。@react-native-picker/picker を使う。

npx expo install @react-native-picker/picker

日付・時刻

npx expo install @react-native-community/datetimepicker
import DateTimePicker from "@react-native-community/datetimepicker"

<DateTimePicker
  value={date}
  mode="date"   // "date" | "time" | "datetime"
  display="default"
  onChange={(event, selectedDate) => setDate(selectedDate ?? date)}
/>

キーボードのエッジケース

iPad のスプリットキーボード

useKeyboard や Keyboard Controller の API でキーボード高さを取得すると安全。

外部キーボード接続時

ハードウェアキーボードがあると onSubmitEditing がうまく動かないことがある。 onKeyPress"Enter" を見るのが代替策。

Android の windowSoftInputMode

app.jsonandroid.softwareKeyboardLayoutMode"pan"(既定)/ "resize" を選べる。 スクロールビューで挙動がおかしい時はここを切り替えると治ることがある。

アクセシビリティ

送信ボタンのトグル

通信中は2重送信を防ぐのと、ローディングを表示するのが基本。

<Pressable disabled={mutation.isPending} onPress={handleSubmit(onSubmit)}>
  {mutation.isPending ? <ActivityIndicator /> : <Text>送信</Text>}
</Pressable>
フォーム最低ライン

✓ 適切な keyboardType / autoCapitalize / autoComplete
✓ Next で次のフィールドへ飛べる
✓ 外タップで閉じる、または KeyboardAvoidingView で隠れない
✓ エラーメッセージが視覚 + アクセシビリティで伝わる
✓ 送信中は2重送信を防ぐ