Keyboard actions are part of a form’s interaction contract, not a cosmetic detail. On a phone, on Android TV, or with a physical keyboard, a useful IME action can remove an unnecessary focus change and make repeated data entry much less tiring.
Choose the action from the user’s next step
| Action | Use it when |
|---|---|
None |
The keyboard should expose no special action. |
Default |
The input method should choose the default behavior. |
Done |
The user has completed the form or field group. |
Go |
The field starts navigation to a destination. |
Next / Previous |
Focus should move through a known field sequence. |
Search |
The value submits a search. |
Send |
The value sends a message or payload. |
The important question is: what is the next meaningful state after this value is entered? Next should move to the next field; it should not silently submit a partially complete form.
Jetpack Compose
Set the visual keyboard contract with KeyboardOptions, then implement the transition with KeyboardActions:
val focusManager = LocalFocusManager.current
TextField(
value = email,
onValueChange = onEmailChanged,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next,
),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) },
),
)
For the last field, prefer Done or the domain action that is actually available. Keep the submit action available as a visible button too: hardware keyboards, accessibility services, and test environments do not all expose the same IME UI.
Views and XML
The equivalent XML contract is android:imeOptions, with an editor listener for the action:
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionSearch"
android:inputType="textEmailAddress"
android:maxLines="1" />
binding.etEmail.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
viewModel.search(binding.etEmail.text.toString())
true
} else {
false
}
}
Returning true only when the action was handled avoids swallowing an event that another part of the form still needs.
A small test matrix
Before calling the form done, test the action on the emulator and with an external keyboard. Check that Next moves focus, Done dismisses or completes the intended step, Search performs the same operation as the visible search button, and validation errors leave focus and the error message understandable. This is especially valuable in field workflows where the same form is filled dozens of times a day.
The principle scales beyond a single EditText: a good mobile flow makes the next state obvious, available, and testable.
