Skip to main content

TextField

iOS Android
TextField on iOS TextField on Android

Native text field using TextInputLayout with the Material 3 styles on Android and UITextField (a growing UITextView for multi-line text) on iOS. The whole field is the platform widget, not a TextInput dressed up in JavaScript: Android gets the floating label, the outlined or filled box, supporting text, the error state, start and end icons, prefix and suffix text and the character counter; iOS gets the text traits React Native's TextInput doesn't expose, Writing Tools, inline predictions and smart punctuation among them.

import { TextField } from 'react-native-platform-components';

const [email, setEmail] = useState('');

<TextField
label="Email"
placeholder="you@example.com"
supportingText="We never share it"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoComplete="email"
autoCapitalize="none"
/>;

The props that exist on React Native's TextInput keep their names and meaning (value, onChangeText, placeholder, secureTextEntry, keyboardType, maxLength, editable, focus() and blur() on the ref), so a field is a change of import. Form libraries that drive value and onChangeText work unchanged.

Props

PropTypeDescription
valuestringControlled text. See Controlled text
defaultValuestringInitial text of an uncontrolled field
onChangeText(text: string) => voidCalled with the new text on every edit
onChange(event) => voidCalled on every edit with nativeEvent.text and nativeEvent.eventCount
onFocus, onBlur(event) => voidFocus changes, with nativeEvent.text
onSubmitEditing(event) => voidThe return key was pressed (single-line fields). The field blurs
labelstringField label. See Label, placeholder and supporting text
placeholderstringShown while the field is empty
supportingTextstringHelper text below the field
errorboolean | stringError state; a string is the message. See Validation
prefix, suffixstringText inside the field before and after the input ($, kg)
leadingIconPlatformIconIcon at the start of the field. See Icons
trailingIconPlatformIconIcon at the end of the field; pressing it calls onTrailingIconPress
onTrailingIconPress() => voidThe trailing icon was pressed
clearButtonMode'never' | 'while-editing' | 'unless-editing' | 'always'A clear button inside the field. Default: 'never'
passwordTogglebooleanA button that shows and hides the text of a secureTextEntry field
showCharacterCountbooleanCharacter count below the field, 12 / 100 with maxLength
maxLengthnumberMaximum number of characters
keyboardTypeTextInput valuesKeyboard to show. Default: 'default'
returnKeyTypeTextInput valuesReturn key label. Default: 'default'
autoCapitalize'none' | 'sentences' | 'words' | 'characters'Default: 'sentences'
autoCorrectbooleanAuto-correction and suggestions. Default: true
secureTextEntrybooleanObscures the text
multilinebooleanA field that grows with its text. Return inserts a newline
editablebooleanDefault: true. A non-editable field is drawn disabled
autoFocusbooleanFocuses the field when it mounts
selectTextOnFocusbooleanSelects all text on focus
autoComplete'email' | 'password' | 'one-time-code' | ...Autofill hint. See Autofill
keyboardAppearance'default' | 'light' | 'dark'iOS keyboard appearance
textStyle{ fontFamily?, fontSize?, fontWeight?, fontStyle? }Font of the input text
accessibilityLabelstringScreen-reader label. Defaults to label

Ref

MethodDescription
focus()Focuses the field and shows the keyboard
blur()Removes focus and hides the keyboard
clear()Clears the text; onChangeText is called with ''
isFocused()Whether the field has focus
const field = useRef<TextFieldRef>(null);
<TextField ref={field} label="Name" />
<Button label="Edit" onPress={() => field.current?.focus()} />

iOS Props (ios)

The text traits, each 'default' unless set. They apply on the iOS version that introduced them and are ignored before it.

PropTypeDescription
writingTools'default' | 'complete' | 'limited' | 'none'Writing Tools (iOS 18): rewrites in place, only the panel, or off
inlinePrediction'default' | 'yes' | 'no'Inline predictive text (iOS 17)
smartQuotes'default' | 'yes' | 'no'Smart quotes
smartDashes'default' | 'yes' | 'no'Smart dashes
smartInsertDelete'default' | 'yes' | 'no'Spaces around pasted and deleted words
mathExpressionCompletion'default' | 'yes' | 'no'Math expression completion, 1+1= (iOS 18)
borderStyle'roundedRect' | 'none' | 'line' | 'bezel'UITextField.borderStyle. Default: 'roundedRect'
labelPlacement'above' | 'leading'The label above the field (default) or in a leading column. See Grouped forms on iOS
labelWidthnumberWidth of the leading label column, in points. Default: 100

Android Props (android)

PropTypeDescription
material'm3' | 'system'The Material 3 text field (default) or the platform EditText. See Material style
variant'outlined' | 'filled'Material 3 text field style. Default: 'outlined'. See Variants
densebooleanThe dense variant, a shorter field

Controlled text

value and onChangeText work like the core TextInput, including under fast typing. Native keeps a counter of user edits and sends it with every onChangeText; a value pushed from JS carries the last counter JS has seen, and native drops it when the user has typed since, so a slow render never erases keystrokes. The edit that follows reconciles the two sides. Formatting as you type (onChangeText={(t) => setValue(t.toUpperCase())}) works, and a field whose owner ignores an edit is reverted to value, as TextInput does.

The field registers with React Native's focus tracking, so Keyboard.dismiss(), a ScrollView's keyboardShouldPersistTaps and KeyboardAvoidingView treat it like a TextInput.

Label, placeholder and supporting text

label is the field's name. On Android it is the Material floating label: inside the box while empty, above the text once focused or filled. On iOS it is a caption above the field that takes the tint color while focused. placeholder is shown inside the empty field (on Android only while focused when there is a label, so the two don't overlap). supportingText sits below the field on both platforms.

<TextField label="Username" placeholder="At least 3 characters" supportingText="Public" />

A field without a label has the placeholder as its only hint and, on Android, the shorter box of a plain field.

Validation

error puts the field in the error state: red outline, label and supporting text on Android, red caption and message on iOS. A string replaces the supporting text with the message; true keeps the supporting text and only changes the colors. On Android the Material error icon takes the end of the field while the error shows, unless the field has a passwordToggle or a trailingIcon, which stay in place.

<TextField
label="Email"
value={email}
onChangeText={setEmail}
onBlur={() => setTouched(true)}
error={touched && !isValid(email) ? 'Enter a valid email address' : undefined}
supportingText="Work address"
/>

Icons

leadingIcon and trailingIcon accept the same shapes as Button: an SF Symbol or drawable name, an image asset, or an { ios, android } pair. The trailing icon is a button; onTrailingIconPress is called when it is pressed.

<TextField
placeholder="Search"
leadingIcon={{
ios: { type: 'sfSymbol', name: 'magnifyingglass' },
android: { type: 'drawable', name: 'search' },
}}
trailingIcon={{
ios: { type: 'sfSymbol', name: 'paperplane' },
android: { type: 'drawable', name: 'send' },
}}
onTrailingIconPress={search}
returnKeyType="search"
onSubmitEditing={search}
/>

The end of the field holds one thing: a trailingIcon, else the passwordToggle, else the clear button. On Android the clear button is the Material clear icon, shown while the focused field has text, whatever clearButtonMode says beyond 'never'. Multi-line fields have no icons, prefix or suffix on iOS, where a UITextView has no accessory slots.

Passwords

<TextField
label="Password"
secureTextEntry
passwordToggle
autoComplete="new-password"
value={password}
onChangeText={setPassword}
/>

passwordToggle adds the Material password toggle on Android and an eye button on iOS. autoComplete="password" and "new-password" enable the platform password managers; "one-time-code" offers codes from incoming messages.

Autofill

autoComplete maps to Android autofill hints and to textContentType on iOS: off, username, password, new-password, one-time-code, email, name, given-name, family-name, tel, street-address, postal-code, country, cc-number, cc-exp, cc-csc, url.

Multi-line text

<TextField label="Notes" multiline maxLength={200} showCharacterCount />

A multi-line field grows with its text; give it a minHeight style for a taller box. The return key inserts a newline, so onSubmitEditing is not called.

Grouped forms on iOS

Apple's own forms (Contacts, Settings) put the label in a column at the left and a borderless field beside it, one row per field inside a rounded group. That is the same UITextField in a list cell layout; ios.labelPlacement: 'leading' with ios.borderStyle: 'none' gives the row, and your own grouped container with hairline separators gives the card. Rows in one group share a labelWidth so the fields line up. Android keeps its Material fields, so the same code is a stack of outlined fields there.

const row = { ios: { labelPlacement: 'leading', borderStyle: 'none' } } as const;

<View style={styles.group}>
<TextField label="First" value={first} onChangeText={setFirst} {...row} />
<Divider />
<TextField label="Middle" placeholder="optional" value={middle} onChangeText={setMiddle} {...row} />
<Divider />
<TextField label="Last" value={last} onChangeText={setLast} {...row} />
</View>

Variants

Material 3 has two text field styles, both on android.variant: 'outlined' (the default) draws a stroke around the box, 'filled' a tinted container with a bottom line. android.dense picks the shorter, dense version of either. iOS has one field; ios.borderStyle chooses between the rounded rectangle and the other UITextField borders.

<TextField label="Filled" android={{ variant: 'filled' }} />
<TextField label="Dense" android={{ dense: true }} />

Material style

android.material is the same preference as on DatePicker and SelectionMenu: 'm3' (the default here) is the Material 3 TextInputLayout; 'system' is the platform EditText, the AppCompat widget with the underline, for screens that keep the system look. The system field shows one hint (the placeholder, else the label), the supporting or error text on a line below and the icons as compound drawables; it has no floating label, box, clear button, password toggle, counter, prefix or suffix. variant and dense only apply to the Material field.

<TextField label="Email" supportingText="Work address" android={{ material: 'system' }} />

Styling

textStyle sets the input font with the Text style conventions; the label, supporting text and counter keep the platform's typography (Dynamic Type on iOS). Colors come from the theme: the app's Material 3 theme or the brand color set with useNativeTheme on Android, the tint color on iOS.

Android theme

The field is a Material 3 widget, so it works with a Theme.Material3 app theme and with the library's Material 3 fallback; see Android Theme Configuration.