---
modificationDate: May 19, 2026
title: TextField
description: Jetpack Compose TextField components for native Material3 text input.
sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-ui'
packageName: '@expo/ui'
platforms: ['android', 'expo-go']
---

<AgentInstructions>

## Submitting Feedback

If this page contains errors, outdated information, or gaps that blocked you from completing a task, report it so the docs can be improved:

curl -X POST https://api.expo.dev/v2/feedback/docs-send -H 'Content-Type: application/json' -d '{"url":"/versions/unversioned/sdk/ui/jetpack-compose/textfield/","feedback":"🤖 Agent feedback: <specific, actionable description>"}'

Only submit when you have something specific and actionable to report.

</AgentInstructions>

> This is documentation for the next SDK version. For up-to-date documentation, see the [latest version](/versions/latest/sdk/ui/jetpack-compose/textfield) (SDK 56).

# TextField

Jetpack Compose TextField components for native Material3 text input.
Android, Included in Expo Go

> For the complete documentation index, see [llms.txt](/llms.txt). Use this file to discover all available pages.

> For cross-platform usage, see the universal [`TextInput`](/versions/unversioned/sdk/ui/universal/textinput) — it renders the appropriate native component per platform.

Expo UI provides two text field components that match the official Jetpack Compose [TextField API](https://developer.android.com/develop/ui/compose/text/user-input): `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

```sh
npx expo install @expo/ui
```

If you are installing this in an [existing React Native app](/bare/overview), make sure to [install `expo`](/bare/installing-expo-modules) in your project.

## Usage

### Basic text field

A filled text field is the default Material3 text input style.

```tsx
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.

```tsx
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`.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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`](/versions/unversioned/sdk/ui/jetpack-compose/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-reanimated`](https://docs.swmansion.com/react-native-reanimated/) and [`react-native-worklets`](https://docs.swmansion.com/react-native-worklets/).

```tsx
import { Host, TextField, Text, useNativeState } from '@expo/ui/jetpack-compose';
import { fillMaxWidth } from '@expo/ui/jetpack-compose/modifiers';
import { useEffectEvent } from 'react';

export default function WorkletPhoneMaskExample() {
  const phone = useNativeState('');
  const selection = useNativeState({ start: 0, end: 0 });

  const handleValueChange = useEffectEvent((v: string) => {
    '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;
      // Snaps to end for demo. Real masks need smarter cursor handling.
      selection.value = { start: formatted.length, end: formatted.length };
    }
  });

  return (
    <Host matchContents>
      <TextField
        value={phone}
        selection={selection}
        keyboardOptions={{ keyboardType: 'phone' }}
        modifiers={[fillMaxWidth()]}
        onValueChange={handleValueChange}>
        <TextField.Placeholder>
          <Text>(555) 123-4567</Text>
        </TextField.Placeholder>
      </TextField>
    </Host>
  );
}
```

## API

```tsx
import { TextField, OutlinedTextField } from '@expo/ui/jetpack-compose';
```

## Components

### `OutlinedTextField`

Supported platforms: Android.

Type: React.[Element](https://www.typescriptlang.org/docs/handbook/jsx.html#function-component)<[OutlinedTextFieldProps](#outlinedtextfieldprops)\>

A Material3 `OutlinedTextField` with a transparent background and border outline.

OutlinedTextFieldProps

### `colors`

Supported platforms: Android.

Optional • Type: [TextFieldColors](#textfieldcolors)

#### Inherited Props

-   [BaseTextFieldProps](#basetextfieldprops)

### `TextField`

Supported platforms: Android.

Type: React.[Element](https://www.typescriptlang.org/docs/handbook/jsx.html#function-component)<[TextFieldProps](#textfieldprops)\>

A Material3 `TextField`.

TextFieldProps

### `colors`

Supported platforms: Android.

Optional • Type: [TextFieldColors](#textfieldcolors)

#### Inherited Props

-   [BaseTextFieldProps](#basetextfieldprops)

## Types

### `ObservableState`

Supported platforms: Android.

Observable state shared between JavaScript and native views (Jetpack Compose on Android and SwiftUI on iOS).

Type: [SharedObject](/versions/unversioned/sdk/expo#sharedobjecttype) extended by:

| Property | Type | Description |
| --- | --- | --- |
| onChange | `[listener] | null` | A single listener invoked on the native UI runtime whenever the value changes (after iOS `didSet` and Android's setter). Assigning replaces the previous listener; assign `null` to clear. The initial value does not fire `onChange`. The callback must be a worklet so it can run synchronously on the UI thread. Attach it inside `useEffect` and clear it in the cleanup so the listener lifecycle matches the component lifecycle. . Example
```tsx
const state = useNativeState(0);

useEffect(() => {
  state.onChange = (value) => {
    'worklet';
    console.log('changed to', value);
  };
  return () => {
    state.onChange = null;
  };
}, []);
```

 |
| value | `T` | The current value. Writes from a UI worklet are synchronous and immediately readable. Writes from the JS thread are scheduled to the UI thread asynchronously, the new value is not readable until the update has been applied. Prefer writing from a worklet when you need synchronous updates |

### `TextFieldCapitalization`

Supported platforms: Android.

Literal Type: `string`

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

### `TextFieldColors`

Supported platforms: Android.

Colors for `TextField` and `OutlinedTextField`. Maps to `TextFieldColors` in Compose, shared by both variants.

| Property | Type | Description |
| --- | --- | --- |
| cursorColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledContainerColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledIndicatorColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledLabelColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledLeadingIconColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledPlaceholderColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledPrefixColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledSuffixColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledSupportingTextColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledTextColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| disabledTrailingIconColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorContainerColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorCursorColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorIndicatorColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorLabelColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorLeadingIconColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorPlaceholderColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorPrefixColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorSuffixColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorSupportingTextColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorTextColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| errorTrailingIconColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedContainerColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedIndicatorColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedLabelColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedLeadingIconColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedPlaceholderColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedPrefixColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedSuffixColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedSupportingTextColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedTextColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| focusedTrailingIconColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedContainerColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedIndicatorColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedLabelColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedLeadingIconColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedPlaceholderColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedPrefixColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedSuffixColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedSupportingTextColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedTextColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |
| unfocusedTrailingIconColor(optional) | [ColorValue](https://reactnative.dev/docs/colors) | - |

### `TextFieldImeAction`

Supported platforms: Android.

Literal Type: `string`

Acceptable values are: `'default'` | `'none'` | `'go'` | `'search'` | `'send'` | `'previous'` | `'next'` | `'done'`

### `TextFieldKeyboardActions`

Supported platforms: Android.

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` | - |

### `TextFieldKeyboardOptions`

Supported platforms: Android.

Keyboard options matching Compose `KeyboardOptions`.

| Property | Type | Description |
| --- | --- | --- |
| autoCorrectEnabled(optional) | `boolean` | Default: `true` |
| capitalization(optional) | [TextFieldCapitalization](#textfieldcapitalization) | Default: `'none'` |
| imeAction(optional) | [TextFieldImeAction](#textfieldimeaction) | Default: `'default'` |
| keyboardType(optional) | [TextFieldKeyboardType](#textfieldkeyboardtype) | Default: `'text'` |

### `TextFieldKeyboardType`

Supported platforms: Android.

Literal Type: `string`

Acceptable values are: `'text'` | `'number'` | `'email'` | `'phone'` | `'decimal'` | `'password'` | `'ascii'` | `'uri'` | `'numberPassword'`

### `TextFieldRef`

Supported platforms: Android.

Can be used for imperatively focusing and setting text/selection on the `TextField` component.

| Property | Type | Description |
| --- | --- | --- |
| blur | () => [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<void\> | - |
| clear | () => [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<void\> | Clear the current text. |
| focus | () => [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<void\> | - |
| setSelection | (start: number, end: number) => [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<void\> | Programmatically set the selection range. |
| setText | (newText: string) => [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<void\> | - |
