TextField
Jetpack Compose TextField components for native Material3 text input.
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
Expo UI provides two text field components that match the official Jetpack Compose TextField API: TextField (filled) and OutlinedTextField (outlined border). Both variants share the same props and support composable slot children for label, placeholder, icons, prefix, suffix, and supporting text.
| Type | Appearance | Purpose |
|---|---|---|
| Filled | Solid background with a bottom indicator line. | Default text input style following Material3 design. Use for most forms and input fields. |
| Outlined | Transparent background with a border outline. | Alternative style that provides a distinct visual boundary. Use when filled fields blend into the background. |
Installation
-Â npx expo install @expo/uiIf you are installing this in an existing React Native app, make sure to install expo in your project.
Usage
Basic text field
A filled text field is the default Material3 text input style.
import { useState } from 'react'; import { Host, TextField, Text } from '@expo/ui/jetpack-compose'; export default function BasicTextFieldExample() { const [value, setValue] = useState(''); return ( <Host matchContents> <TextField onValueChange={setValue}> <TextField.Label> <Text>Username</Text> </TextField.Label> </TextField> </Host> ); }
Outlined text field
Use OutlinedTextField for a text field with a border outline instead of a filled background.
import { useState } from 'react'; import { Host, OutlinedTextField, Text } from '@expo/ui/jetpack-compose'; export default function OutlinedTextFieldExample() { const [value, setValue] = useState(''); return ( <Host matchContents> <OutlinedTextField onValueChange={setValue}> <OutlinedTextField.Label> <Text>Email</Text> </OutlinedTextField.Label> <OutlinedTextField.Placeholder> <Text>you@example.com</Text> </OutlinedTextField.Placeholder> </OutlinedTextField> </Host> ); }
Slots
Both TextField and OutlinedTextField support 7 composable slots that match the Compose API: Label, Placeholder, LeadingIcon, TrailingIcon, Prefix, Suffix, and SupportingText.
import { useState } from 'react'; import { Host, TextField, Text } from '@expo/ui/jetpack-compose'; export default function TextFieldSlotsExample() { const [value, setValue] = useState(''); return ( <Host matchContents> <TextField onValueChange={setValue}> <TextField.Label> <Text>Price</Text> </TextField.Label> <TextField.Placeholder> <Text>0.00</Text> </TextField.Placeholder> <TextField.LeadingIcon> <Text>💰</Text> </TextField.LeadingIcon> <TextField.Prefix> <Text>$</Text> </TextField.Prefix> <TextField.Suffix> <Text>USD</Text> </TextField.Suffix> <TextField.SupportingText> <Text>Enter the amount</Text> </TextField.SupportingText> </TextField> </Host> ); }
Keyboard options
Use the keyboardOptions prop to configure the keyboard type, capitalization, auto-correct, and IME action.
import { useState } from 'react'; import { Host, TextField, Text } from '@expo/ui/jetpack-compose'; export default function KeyboardOptionsExample() { const [value, setValue] = useState(''); return ( <Host matchContents> <TextField onValueChange={setValue} singleLine keyboardOptions={{ keyboardType: 'email', capitalization: 'none', autoCorrectEnabled: false, imeAction: 'done', }}> <TextField.Label> <Text>Email</Text> </TextField.Label> </TextField> </Host> ); }
Keyboard actions
Use the keyboardActions prop to handle IME action button presses. The triggered callback depends on the imeAction set in keyboardOptions. Each callback receives the current text value.
import { useState } from 'react'; import { Host, TextField, Text } from '@expo/ui/jetpack-compose'; export default function KeyboardActionsExample() { const [value, setValue] = useState(''); const [submitted, setSubmitted] = useState(''); return ( <Host matchContents> <TextField onValueChange={setValue} singleLine keyboardOptions={{ imeAction: 'search' }} keyboardActions={{ onSearch: text => setSubmitted(text), }}> <TextField.Label> <Text>Search</Text> </TextField.Label> </TextField> </Host> ); }
Error state
Set isError to display the text field in an error state. Combine with SupportingText to show an error message.
import { useState } from 'react'; import { Host, OutlinedTextField, Text } from '@expo/ui/jetpack-compose'; export default function ErrorStateExample() { const [value, setValue] = useState(''); const hasError = value.length > 0 && !value.includes('@'); return ( <Host matchContents> <OutlinedTextField onValueChange={setValue} isError={hasError} singleLine> <OutlinedTextField.Label> <Text>Email</Text> </OutlinedTextField.Label> <OutlinedTextField.SupportingText> <Text>{hasError ? 'Please enter a valid email' : 'Required'}</Text> </OutlinedTextField.SupportingText> </OutlinedTextField> </Host> ); }
Imperative ref
Use a ref to imperatively set text, clear the field, change the selection, or move focus.
import { useRef, useState } from 'react'; import { Host, TextField, TextFieldRef, Button, Row, Text, Column } from '@expo/ui/jetpack-compose'; import { padding } from '@expo/ui/jetpack-compose/modifiers'; export default function ImperativeRefExample() { const ref = useRef<TextFieldRef>(null); const [value, setValue] = useState(''); return ( <Host matchContents> <Column> <TextField ref={ref} onValueChange={setValue} singleLine> <TextField.Label> <Text>Name</Text> </TextField.Label> </TextField> <Row horizontalArrangement={{ spacedBy: 8 }} modifiers={[padding(8, 0, 0, 0)]}> <Button onClick={() => ref.current?.setText('Hello world')}> <Text>Set text</Text> </Button> <Button onClick={() => ref.current?.clear()}> <Text>Clear</Text> </Button> <Button onClick={() => ref.current?.setSelection(0, 5)}> <Text>Select first word</Text> </Button> </Row> <Row horizontalArrangement={{ spacedBy: 8 }} modifiers={[padding(8, 0, 0, 0)]}> <Button onClick={() => ref.current?.focus()}> <Text>Focus</Text> </Button> <Button onClick={() => ref.current?.blur()}> <Text>Blur</Text> </Button> </Row> </Column> </Host> ); }
Worklet text masking
When onValueChange is marked with the 'worklet' directive, it runs synchronously on the UI thread, so writes to useNativeState observables inside the callback take effect before the next frame. There is no flicker between the typed text and the masked text. The example below masks a phone number as the user types and writes both value and selection from the worklet to keep the cursor at the end of the formatted value.
Note: Worklets require installing
react-native-reanimatedandreact-native-worklets.
import { Host, TextField, Text, useNativeState } from '@expo/ui/jetpack-compose'; import { fillMaxWidth } from '@expo/ui/jetpack-compose/modifiers'; export default function WorkletPhoneMaskExample() { const phone = useNativeState(''); const selection = useNativeState({ start: 0, end: 0 }); return ( <Host matchContents> <TextField value={phone} selection={selection} keyboardOptions={{ keyboardType: 'phone' }} modifiers={[fillMaxWidth()]} onValueChange={v => { 'worklet'; const digits = v.replace(/\D/g, '').slice(0, 10); let formatted = digits; if (digits.length > 6) { formatted = `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`; } else if (digits.length > 3) { formatted = `(${digits.slice(0, 3)}) ${digits.slice(3)}`; } if (formatted !== v) { phone.value = formatted; selection.value = { start: formatted.length, end: formatted.length }; } }}> <TextField.Placeholder> <Text>(555) 123-4567</Text> </TextField.Placeholder> </TextField> </Host> ); }
API
import { TextField, OutlinedTextField } from '@expo/ui/jetpack-compose';
Components
Type: React.Element<OutlinedTextFieldProps>
A Material3 OutlinedTextField with a transparent background and border outline.
TextFieldColorsType: React.Element<TextFieldProps>
A Material3 TextField.
TextFieldColorsTypes
Observable state shared between JavaScript and native views (Jetpack Compose on Android and SwiftUI on iOS).
Type: SharedObject extended by:
| Property | Type | Description |
|---|---|---|
| value | T | The current value. Reads are safe from any thread; prefer writing from a worklet so the update runs on the native UI thread. Updating state from the JS thread might show a development warning. |
Literal Type: string
Acceptable values are: 'none' | 'characters' | 'words' | 'sentences'
Colors for TextField and OutlinedTextField.
Maps to TextFieldColors in Compose, shared by both variants.
| Property | Type | Description |
|---|---|---|
| cursorColor(optional) | ColorValue | - |
| disabledContainerColor(optional) | ColorValue | - |
| disabledIndicatorColor(optional) | ColorValue | - |
| disabledLabelColor(optional) | ColorValue | - |
| disabledLeadingIconColor(optional) | ColorValue | - |
| disabledPlaceholderColor(optional) | ColorValue | - |
| disabledPrefixColor(optional) | ColorValue | - |
| disabledSuffixColor(optional) | ColorValue | - |
| disabledSupportingTextColor(optional) | ColorValue | - |
| disabledTextColor(optional) | ColorValue | - |
| disabledTrailingIconColor(optional) | ColorValue | - |
| errorContainerColor(optional) | ColorValue | - |
| errorCursorColor(optional) | ColorValue | - |
| errorIndicatorColor(optional) | ColorValue | - |
| errorLabelColor(optional) | ColorValue | - |
| errorLeadingIconColor(optional) | ColorValue | - |
| errorPlaceholderColor(optional) | ColorValue | - |
| errorPrefixColor(optional) | ColorValue | - |
| errorSuffixColor(optional) | ColorValue | - |
| errorSupportingTextColor(optional) | ColorValue | - |
| errorTextColor(optional) | ColorValue | - |
| errorTrailingIconColor(optional) | ColorValue | - |
| focusedContainerColor(optional) | ColorValue | - |
| focusedIndicatorColor(optional) | ColorValue | - |
| focusedLabelColor(optional) | ColorValue | - |
| focusedLeadingIconColor(optional) | ColorValue | - |
| focusedPlaceholderColor(optional) | ColorValue | - |
| focusedPrefixColor(optional) | ColorValue | - |
| focusedSuffixColor(optional) | ColorValue | - |
| focusedSupportingTextColor(optional) | ColorValue | - |
| focusedTextColor(optional) | ColorValue | - |
| focusedTrailingIconColor(optional) | ColorValue | - |
| unfocusedContainerColor(optional) | ColorValue | - |
| unfocusedIndicatorColor(optional) | ColorValue | - |
| unfocusedLabelColor(optional) | ColorValue | - |
| unfocusedLeadingIconColor(optional) | ColorValue | - |
| unfocusedPlaceholderColor(optional) | ColorValue | - |
| unfocusedPrefixColor(optional) | ColorValue | - |
| unfocusedSuffixColor(optional) | ColorValue | - |
| unfocusedSupportingTextColor(optional) | ColorValue | - |
| unfocusedTextColor(optional) | ColorValue | - |
| unfocusedTrailingIconColor(optional) | ColorValue | - |
Literal Type: string
Acceptable values are: 'default' | 'none' | 'go' | 'search' | 'send' | 'previous' | 'next' | 'done'
Keyboard actions matching Compose KeyboardActions.
The triggered callback depends on the imeAction in keyboardOptions.
| Property | Type | Description |
|---|---|---|
| onDone(optional) | (value: string) => void | - |
| onGo(optional) | (value: string) => void | - |
| onNext(optional) | (value: string) => void | - |
| onPrevious(optional) | (value: string) => void | - |
| onSearch(optional) | (value: string) => void | - |
| onSend(optional) | (value: string) => void | - |
Keyboard options matching Compose KeyboardOptions.
| Property | Type | Description |
|---|---|---|
| autoCorrectEnabled(optional) | boolean | Default: true |
| capitalization(optional) | TextFieldCapitalization | Default: 'none' |
| imeAction(optional) | TextFieldImeAction | Default: 'default' |
| keyboardType(optional) | TextFieldKeyboardType | Default: 'text' |
Literal Type: string
Acceptable values are: 'text' | 'number' | 'email' | 'phone' | 'decimal' | 'password' | 'ascii' | 'uri' | 'numberPassword'
Can be used for imperatively focusing and setting text/selection on the TextField component.