Question

Close the Virtual Keyboard in Jetpack Compose TextField

kotlinandroidandroid-jetpack-composeandroid-input-methodandroid-compose-textfield

Question

How can I close the Android virtual keyboard when a user presses the Done IME action in a Jetpack Compose TextField?

val text = remember { mutableStateOf("") }

TextField(
    value = text.value,
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
    onValueChange = { value -> text.value = value }
)

I want to run code that hides the keyboard when the user presses the action button shown by ImeAction.Done.

Short Answer

You will learn how IME actions work in Jetpack Compose and how to respond to the keyboard's Done button. You will use KeyboardActions to receive the action, LocalSoftwareKeyboardController to request that the software keyboard hide, and LocalFocusManager to clear focus when appropriate.

Concept

A TextField can ask Android's input method editor (IME), usually the on-screen keyboard, to show a particular action button. For example, ImeAction.Done commonly displays a Done button instead of an Enter or Next button.

There are two separate responsibilities:

  1. Configure the keyboard action with KeyboardOptions(imeAction = ImeAction.Done).
  2. Handle the action with KeyboardActions(onDone = { ... }).

Inside onDone, use LocalSoftwareKeyboardController.current?.hide() to request that the software keyboard closes. The controller is nullable because a software keyboard controller may not be available in every environment, such as some tests or hardware-keyboard situations.

In forms, it is also common to call focusManager.clearFocus(). Clearing focus removes the cursor and usually causes the keyboard to disappear naturally. This can be more appropriate than hiding the keyboard alone, because a focused text field may request the keyboard again.

Mental Model

Think of the IME action as the label and behavior of the keyboard's bottom-right key.

  • KeyboardOptions is the instruction card you give to the keyboard: “Show a Done key.”
  • KeyboardActions is the button handler: “When Done is pressed, run this code.”
  • keyboardController.hide() is a request to lower the keyboard.
  • focusManager.clearFocus() is like moving the cursor away from the input box, so there is no longer an active field needing typing.

The keyboard is controlled by the Android system, so hiding it is a request rather than a permanent guarantee.

Take Quiz

Syntax and Examples

Use KeyboardOptions to select an IME action and KeyboardActions to handle it.

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.TextField
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardActions
import androidx.compose.ui.text.input.KeyboardOptions

@Composable
fun NameField() {
    var name by remember { mutableStateOf("") }
    val keyboardController = LocalSoftwareKeyboardController.current
    val focusManager: FocusManager = LocalFocusManager.current

    TextField(
        value = name,
        onValueChange = { name = it },
        modifier = Modifier.fillMaxWidth(),
        label = { Text("Name") },
        keyboardOptions = KeyboardOptions(
            imeAction = ImeAction.Done
        ),
        keyboardActions = KeyboardActions(
            onDone = {
                // Perform any validation or submission first if needed.
                focusManager.clearFocus()
                keyboardController?.hide()
            }
        )
    )
}

onDone runs when the user activates the keyboard's Done action. Clearing focus removes the active cursor; hide() explicitly asks Android to close the software keyboard.

In older Compose code, you may see imeAction and onImeActionPerformed directly on TextField. Those APIs have been replaced by keyboardOptions and keyboardActions in current Compose code.

Step by Step Execution

Consider this small example:

keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
    onDone = {
        saveProfile()
        focusManager.clearFocus()
        keyboardController?.hide()
    }
)

When the field is displayed:

  1. KeyboardOptions(imeAction = ImeAction.Done) tells Android that this input is the final step, so the keyboard may show Done.
  2. The user types text into the field.
  3. The user presses Done on the keyboard.
  4. Compose invokes the onDone lambda.
  5. saveProfile() runs your application-specific work.
  6. focusManager.clearFocus() removes focus from the TextField, removing its cursor.
  7. keyboardController?.hide() requests that Android hide the on-screen keyboard.

The ?. operator means “call hide() only when a keyboard controller exists.”

Real World Use Cases

  • Login screen: After entering a password and pressing Done, submit the sign-in request and remove focus from the password field.
  • Search box: Pressing Search can run a query, then hide the keyboard to show more results.
  • Single-field form: A user enters a display name, presses Done, and the app saves the value.
  • Checkout form: The final field can submit or validate the form when Done is pressed.
  • Inline editing: After editing a task title, Done saves the title and exits editing mode.

Choose the action that matches the field's purpose. A middle field in a multi-field form usually uses ImeAction.Next, while the final field often uses ImeAction.Done.

Take Quiz

Real Codebase Usage

In production code, the IME callback usually does more than hide a keyboard. It coordinates validation, focus, and submission.

Validate before dismissing

keyboardActions = KeyboardActions(
    onDone = {
        if (email.isBlank()) {
            errorMessage = "Email is required"
            return@KeyboardActions
        }

        submitEmail(email)
        focusManager.clearFocus()
    }
)

Do not clear focus if the input is invalid and you want the user to correct it.

Move through a form with Next

keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(
    onNext = { focusManager.moveFocus(FocusDirection.Down) }
)

Use Next for intermediate fields and Done for the last one.

Avoid duplicate submissions

If onDone starts a network request, protect against repeated taps with UI state such as isSubmitting. Disable the field or ignore the action while the request is already in progress.

Clear focus when a screen is tapped

Many apps also clear focus when the user taps outside a form. This gives consistent keyboard behavior whether the user presses Done or taps elsewhere.

Common Mistakes

Using obsolete TextField parameters in new code

Older Compose examples may use this pattern:

// Older API style; do not copy for current Compose code.
TextField(
    value = text,
    imeAction = ImeAction.Done,
    onImeActionPerformed = { }
)

Use keyboardOptions and keyboardActions instead:

keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { /* ... */ })

Expecting ImeAction.Done to hide the keyboard automatically

ImeAction.Done configures the action button; it does not define your app's behavior. Add KeyboardActions(onDone = { ... }).

Hiding the keyboard but leaving the field focused

keyboardController?.hide()

This can be enough for a temporary hide, but the cursor remains in the field. For a completed form, also use:

focusManager.clearFocus()

Clearing focus before showing validation errors

If validation fails, clearing focus can make correction less convenient. Keep focus on the invalid field, show an error, and only clear focus after successful validation.

Assuming the keyboard always has a Done button

Android keyboard apps and hardware keyboards can behave differently. Treat IME actions as a helpful interaction path, not the only way to submit important work. A visible Save or Submit button should still work.

Comparisons

OptionPurposeTypical use
ImeAction.DoneFinishes inputFinal form field or inline edit
ImeAction.NextMoves to another fieldFirst and middle form fields
ImeAction.SearchStarts a searchSearch bars
keyboardController?.hide()Requests that the software keyboard closeKeep current focus but temporarily hide keyboard
focusManager.clearFocus()Removes focus from the active fieldFinish editing and remove cursor
focusManager.moveFocus(...)Transfers focus to another fieldMulti-field forms

In many final-field cases, clearFocus() is the primary action and hide() is optional reinforcement. In a search field, you may intentionally keep focus after executing a search so the user can refine the query.

Take Quiz

Cheat Sheet

val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current

TextField(
    value = value,
    onValueChange = { value = it },
    keyboardOptions = KeyboardOptions(
        imeAction = ImeAction.Done
    ),
    keyboardActions = KeyboardActions(
        onDone = {
            focusManager.clearFocus()
            keyboardController?.hide()
        }
    )
)
  • Use KeyboardOptions to choose the IME button type.
  • Use KeyboardActions to react to that button.
  • Use onDone with ImeAction.Done.
  • Call clearFocus() to remove the cursor and finish editing.
  • Call keyboardController?.hide() to request that the software keyboard closes.
  • Use onNext and moveFocus() for fields before the last one.
  • Keep another submit path, such as a button, for accessibility and hardware keyboard users.

FAQ

How do I hide the keyboard on Done in Jetpack Compose?

Set keyboardActions = KeyboardActions(onDone = { ... }), then call LocalSoftwareKeyboardController.current?.hide(). Usually, also call LocalFocusManager.current.clearFocus().

Why is LocalSoftwareKeyboardController.current nullable?

A software keyboard controller is not guaranteed to exist in every environment. The safe-call operator, ?., prevents a crash when it is unavailable.

Does ImeAction.Done automatically close the keyboard?

Not reliably by itself. It asks the IME to show a Done-style action. Your onDone callback should define what happens next.

Should I use hide() or clearFocus()?

Use clearFocus() when editing is complete and you want the cursor removed. Use hide() when you specifically want to request keyboard dismissal. Using both is common for a completed form field.

How do I move to the next TextField instead of closing the keyboard?

Use ImeAction.Next and handle it with KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }).

Why does the keyboard sometimes remain visible?

The keyboard is owned by the Android system and can vary by device, keyboard app, and focus state. Ensure the field is not still focused when your intended behavior is to finish editing.

Is onImeActionPerformed still the recommended API?

No. Current Compose code uses KeyboardOptions and KeyboardActions, such as KeyboardActions(onDone = { ... }).

Take Quiz

Mini Project

Description

Build a small profile-name editor. The user enters a name and presses the keyboard's Done action. The app validates the name, saves it when valid, and closes the editing session by clearing focus and hiding the keyboard.

Goal

Create a Compose text field that saves a non-empty name when the user presses Done and shows an error for blank input.

Requirements

Requirement 1

Take Quiz

Keep learning

Related questions

View more