Question
Close the Virtual Keyboard in Jetpack 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:
- Configure the keyboard action with
KeyboardOptions(imeAction = ImeAction.Done). - 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.
KeyboardOptionsis the instruction card you give to the keyboard: “Show a Done key.”KeyboardActionsis 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.
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:
KeyboardOptions(imeAction = ImeAction.Done)tells Android that this input is the final step, so the keyboard may show Done.- The user types text into the field.
- The user presses Done on the keyboard.
- Compose invokes the
onDonelambda. saveProfile()runs your application-specific work.focusManager.clearFocus()removes focus from theTextField, removing its cursor.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.
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
| Option | Purpose | Typical use |
|---|---|---|
ImeAction.Done | Finishes input | Final form field or inline edit |
ImeAction.Next | Moves to another field | First and middle form fields |
ImeAction.Search | Starts a search | Search bars |
keyboardController?.hide() | Requests that the software keyboard close | Keep current focus but temporarily hide keyboard |
focusManager.clearFocus() | Removes focus from the active field | Finish editing and remove cursor |
focusManager.moveFocus(...) | Transfers focus to another field | Multi-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.
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
KeyboardOptionsto choose the IME button type. - Use
KeyboardActionsto react to that button. - Use
onDonewithImeAction.Done. - Call
clearFocus()to remove the cursor and finish editing. - Call
keyboardController?.hide()to request that the software keyboard closes. - Use
onNextandmoveFocus()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 = { ... }).
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
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.