Reference version

TextInput

A text input backed by native SwiftUI and Jetpack Compose components, with a React Native-compatible API.

Android
iOS
Web
Included in Expo Go
Bundled version:
~56.0.5

For the complete documentation index, see llms.txt. Use this file to discover all available pages.

A text input that routes to TextField from @expo/ui/jetpack-compose on Android, TextField from @expo/ui/swift-ui on iOS, and React Native's TextInput on web.

The API mirrors React Native's TextInput, with two changes: value and selection are observable state objects (created with useNativeState), and onChangeText can be a worklet for synchronously updating the state on the UI thread.

Installation

Terminal
npx expo install @expo/ui

If you are installing this in an existing React Native app, make sure to install expo in your project.

Usage

Uncontrolled

Omit value and the field manages its own text internally. Use onChangeText to observe edits, and use the ref for imperative actions like focus, blur, and clear.

UncontrolledTextInputExample.tsx
import { Button, Column, Host, TextInput, type TextInputRef } from '@expo/ui'; import { useRef } from 'react'; export default function UncontrolledTextInputExample() { const inputRef = useRef<TextInputRef>(null); return ( <Host matchContents={{ vertical: true }}> <Column spacing={8}> <TextInput ref={inputRef} defaultValue="hello" placeholder="Type here" onChangeText={value => console.log(value)} /> <Button label="Clear" onPress={() => inputRef.current?.clear()} /> </Column> </Host> ); }

Controlled

Pass value to drive the field from a useNativeState observable. The example below replaces Hello with World as you type.

ControlledTextInputExample.tsx
import { Host, TextInput, useNativeState } from '@expo/ui'; import { useEffectEvent } from 'react'; export default function ControlledTextInputExample() { const text = useNativeState(''); const handleChangeText = useEffectEvent((value: string) => { 'worklet'; text.value = value === 'Hello' ? 'World' : value; }); return ( <Host matchContents={{ vertical: true }}> <TextInput value={text} placeholder="Type here" onChangeText={handleChangeText} /> </Host> ); }

Worklet masking

Add the 'worklet' directive to onChangeText for synchronously updating the state on the UI thread. Writes to value land without the JS-thread round-trip that can cause cursor flicker.

Note: Worklets require installing react-native-reanimated and react-native-worklets.

PhoneMaskExample.tsx
import { Host, TextInput, useNativeState } from '@expo/ui'; import { useEffectEvent } from 'react'; function formatPhone(input: string) { 'worklet'; const digits = input.replace(/\D/g, '').slice(0, 10); if (digits.length <= 3) return digits; if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`; return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`; } export default function PhoneMaskExample() { const phone = useNativeState(''); const selection = useNativeState({ start: 0, end: 0 }); const handleChangeText = useEffectEvent((value: string) => { 'worklet'; const formatted = formatPhone(value); if (formatted !== value) { phone.value = formatted; // Snaps to end for demo. Real masks need smarter cursor handling. selection.value = { start: formatted.length, end: formatted.length }; } }); return ( <Host matchContents={{ vertical: true }}> <TextInput value={phone} selection={selection} keyboardType="phone-pad" placeholder="(555) 123-4567" onChangeText={handleChangeText} /> </Host> ); }

Unsupported React Native props

Some React Native TextInput props are not supported, because Compose's TextField or SwiftUI's TextField does not expose an equivalent, or because the prop is replaced by a different mechanism. See the API section below for the supported props. If a missing prop blocks your use case, open an issue so it can be prioritized.

API

import { TextInput, useNativeState } from '@expo/ui';

Component

TextInput

Android
iOS
Web

Type: React.Element<TextInputProps>

Props for the TextInput component.

TextInputProps

autoCapitalize

Android
iOS
Web
Optional • Literal type: string • Default: 'sentences'

Controls automatic capitalization of input.

Acceptable values are: 'none' | 'words' | 'sentences' | 'characters'

autoComplete

Android
iOS
Web
Optional • Type: AutoComplete

Autofill hint. iOS maps to textContentType; Android maps to Compose's Modifier.semantics { contentType = ... }.

autoCorrect

Android
iOS
Web
Optional • Type: boolean • Default: true

If false, disables autocorrect / spellcheck suggestions.

autoFocus

Android
iOS
Web
Optional • Type: boolean • Default: false

If true, focuses the input on mount.

caretHidden

Android
iOS
Web
Optional • Type: boolean

If true, the cursor is hidden.

On iOS, this is implemented via tint('transparent'), which also makes the selection highlight invisible. If you set both caretHidden and selectionColor, the caret-hide wins on iOS.

cursorColor

Android
iOS
Web
Optional • Type: ColorValue

Color of the text cursor.

defaultValue

Android
iOS
Web
Optional • Type: string

Initial text shown when the input mounts and value is not provided. Ignored once the user starts typing or if value is set.

editable

Android
iOS
Web
Optional • Type: boolean • Default: true

If false, the input cannot be edited. Selection is still allowed so the user can copy text out of the field.

enterKeyHint

Android
iOS
Web
Optional • Type: EnterKeyHint

HTML-style hint for the keyboard return key. Maps to returnKeyType. When both are set, returnKeyType wins.

inputMode

Android
iOS
Web
Optional • Type: InputMode

HTML-style hint for the keyboard variant. Maps to keyboardType. When both are set, keyboardType wins.

keyboardType

Android
iOS
Web
Optional • Type: KeyboardTypeOptions • Default: 'default'

Determines which keyboard variant is shown.

Lacking native support:

  • iOS: 'visible-password' falls back to the default keyboard.
  • Android: iOS-specific values ('ascii-capable', 'numbers-and-punctuation', 'name-phone-pad', 'twitter', 'web-search') fall back to the text keyboard.

maxLength

Android
iOS
Web
Optional • Type: number

Maximum number of characters allowed.

modifiers

Android
iOS
Optional • Type: ModifierConfig[]

Platform-specific modifier escape hatch. Pass an array of modifier configs from @expo/ui/swift-ui/modifiers or @expo/ui/jetpack-compose/modifiers. Modifiers from the wrong platform are ignored at runtime.

multiline

Android
iOS
Web
Optional • Type: boolean • Default: false

If true, the field accepts multiple lines of input and grows vertically as the user types.

numberOfLines

Android
iOS
Web
Optional • Type: number

Number of lines the field reserves when multiline is true. Forces a fixed visible height of that many lines.

Lacking native support:

  • iOS: requires iOS 16+; below that, the field grows naturally.

onBlur

Android
iOS
Web
Optional • Type: () => void

Called when the field loses focus.

onChangeText

Android
iOS
Web
Optional • Type: (text: string) => void

Called every time the text value changes. Receives the new string.

onContentSizeChange

Android
iOS
Web
Optional • Type: (size: { height: number, width: number }) => void

Called when the rendered size of the input changes. Sizes in points/dp.

Unlike RN's onContentSizeChange, this dispatches the view's outer geometry, including any padding/border applied via style or modifiers. If you use this for autogrow, account for that.

onFocus

Android
iOS
Web
Optional • Type: () => void

Called when the field gains focus.

onSelectionChange

Android
iOS
Web
Optional • Type: (selection: { end: number, start: number }) => void

Called when the text selection range changes.

onSubmitEditing

Android
iOS
Web
Optional • Type: (text: string) => void

Called when the user taps the keyboard return key. Receives the current text in the input.

placeholder

Android
iOS
Web
Optional • Type: string

Placeholder text shown when the field is empty.

placeholderTextColor

Android
iOS
Web
Optional • Type: ColorValue

Color of the placeholder text.

readOnly

Android
iOS
Web
Optional • Type: boolean • Default: false

Alias for editable={false}. When both are set, editable wins.

ref

Android
iOS
Web
Optional • Type: Ref<TextInputRef>

Ref exposing imperative methods (focus, blur, clear).

returnKeyType

Android
iOS
Web
Optional • Type: ReturnKeyTypeOptions

Determines the label of the keyboard return key.

Lacking native support:

  • iOS: 'emergency-call' falls back to the default Return key.
  • Android: 'join', 'route', 'emergency-call' fall back to the default action.

rows

Android
iOS
Web
Optional • Type: number

HTML-style alias for numberOfLines. When both are set, numberOfLines wins.

secureTextEntry

Android
iOS
Web
Optional • Type: boolean • Default: false

If true, the input obscures its text — used for password fields.

  • iOS: backed by SwiftUI's SecureField. The following props are no-ops in this mode: selection, selectTextOnFocus, onSelectionChange, multiline, numberOfLines.
  • Android: backed by Compose's PasswordVisualTransformation.

selection

iOS 18.0+ — pre-ios 18 the prop is ignored.
Optional • Type: ObservableState<{ end: number, start: number }>

Observable state the field writes the current selection to. Create with useNativeState({ start: 0, end: 0 }). Use ref.setSelection(start, end) to set selection programmatically.

selectionColor

Android
iOS
Web
Optional • Type: ColorValue

Color of the selected text highlight. On iOS this also tints the cursor (UIKit's tintColor covers both); pass cursorColor only if you want different cursor color on Android.

selectionHandleColor

Android
Optional • Type: ColorValue

Color of the selection drag handles.

selectTextOnFocus

iOS 18.0+, android, web
Optional • Type: boolean • Default: false

If true, all text is selected when the field gains focus. Implemented via setSelection(0, length) on focus, so if you also pass selection, its value is overwritten on every focus.

style

Android
iOS
Web
Optional • Type: Pick<ViewStyle, 'padding' | 'paddingHorizontal' | 'paddingVertical' | 'paddingTop' | 'paddingBottom' | 'paddingLeft' | 'paddingRight' | 'backgroundColor' | 'borderRadius' | 'borderWidth' | 'borderColor' | 'opacity' | 'width' | 'height'>

Box-level style — sizing, padding, background, border, opacity.

testID

Android
iOS
Web
Optional • Type: string

Identifier used to locate the component in end-to-end tests.

textAlign

Android
iOS
Web
Optional • Literal type: string • Default: 'auto'

Horizontal alignment of the text content.

Lacking native support:

  • iOS: 'justify' is not supported by SwiftUI's TextField and falls back to the default alignment.

Acceptable values are: 'auto' | 'center' | 'left' | 'right' | 'justify'

textStyle

Android
iOS
Web
Optional • Type: { color: string, fontFamily: string, fontSize: number, fontWeight: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900', letterSpacing: number, lineHeight: number, textAlign: 'center' | 'left' | 'right' }

Text-level style — font, color, alignment, spacing.

underlineColorAndroid

Android
Optional • Type: ColorValue

Color of the underline indicator on Android. iOS / web ignore this.

value

Android
iOS
Web
Optional • Type: ObservableState<string>

An observable state holding the current text. Create one with useNativeState('initial value') from @expo/ui. Omit to let the field manage its own internal state.

Types

ObservableState

Android
iOS
Web
PropertyTypeDescription
valueT
-

TextInputRef

Android
iOS
Web

Imperative methods exposed via the TextInput ref.

PropertyTypeDescription
blur() => void

Programmatically blur the input.

clear() => void

Clear the current text.

focus() => void

Programmatically focus the input.

isFocused() => boolean

Returns whether the input currently has focus.

setSelection(start: number, end: number) => Promise<void>
Only for:
iOS 18.0+

Programmatically set the selection range.