Skip to main content

Control focus and hardware keys

Move and clear focus​

Keep each FocusRequester stable with remember, attach it to the destination, and declare directional traversal on the source. Use LocalFocusManager.current for session-wide move or clear operations. A requester must be attached to a mounted target before requestFocus() is called.

fun UiTreeBuilder.CredentialFocusForm() {
val email = rememberTextFieldState()
val password = rememberTextFieldState()
val passwordFocus = remember { FocusRequester() }
val focusManager = LocalFocusManager.current

Column(
modifier = Modifier
.focusGroup()
.onPreviewKeyEvent { event ->
if (event.key == Key.Escape && event.type == KeyEventType.KeyDown) {
focusManager.clearFocus(force = true)
true
} else {
false
}
},
) {
TextField(
state = email,
label = "Email",
modifier = Modifier.focusProperties {
next = passwordFocus
down = passwordFocus
},
)
TextField(
state = password,
label = "Password",
modifier = Modifier.focusRequester(passwordFocus),
)
Button(
text = "Focus password",
onClick = { passwordFocus.requestFocus() },
)
}
}

onPreviewKeyEvent travels from the outermost declarative ancestor to the focused target. An unconsumed event then bubbles from the target through onKeyEvent. Hardware keys are separate from soft-keyboard composition, which continues through TextFieldState and the Android input bridge.

Restore a keyed child​

Call saveFocusedChild() before temporarily removing or recycling a focus group, then call restoreFocusedChild() after it returns. Restoration uses the declarative node key and remains pending until a matching target mounts. Screen or process restoration still belongs to application state followed by an explicit request after mounting.

Keep a pager editor visible​

Vertical scroll owners reveal a focused editor through Android's native child-rectangle chain, even when direct user scrolling is disabled. A pager owns whole-page selection, so place a potentially obscured form in a page-local ScrollableColumn; the enclosing VerticalPager remains controlled by application page state. There is no focus-follow flag. Control unwanted initial movement by deciding which editor receives focus; do not disable the scroll owner's visibility contract. See Modifier architecture for ownership, propagation, and Android mapping invariants.