---
modificationDate: May 06, 2026
title: TextField
description: Jetpack Compose TextField components for native Material3 text input.
sourceCodeUrl: 'https://github.com/expo/expo/tree/sdk-56/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/v56.0.0/sdk/ui/jetpack-compose/textfield/","feedback":"🤖 Agent feedback: <specific, actionable description>"}'

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

</AgentInstructions>

# 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.

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/v56.0.0/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';

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

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

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