android-benchmark-comp…
Use when comparing physical Android benchmark configurations, investigating inconsistent rankings, or selecting an Android default from measured results. Do…
Use when writing or reviewing Jetpack Compose UI for TV, keyboard, desktop, accessibility focus, D-pad navigation, FocusRequester, focusProperties, key events, or initial focus behavior.
$ npx -y skills add chrisbanes/skills --skill compose-focus-navigation --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/compose-focus-navigationContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when writing or reviewing Jetpack Compose UI for TV, keyboard, desktop, accessibility focus, D-pad navigation, FocusRequester, focusProperties, key events, or initial focus behavior.
name: compose-focus-navigation description: Use when writing or reviewing Jetpack Compose UI for TV, keyboard, desktop, accessibility focus, D-pad navigation, FocusRequester, focusProperties, key events, or initial focus behavior.
Focus is stateful UI behavior: make targets and exceptional edges explicit, then drive and verify it through the user's keyboard, D-pad, or remote input.
1. Start with components that already participate in focus. Add a hook only for a requested behavior:
| Need | Add | |---|---| | Normal button/text field/clickable focus | Nothing extra; use the focusable component | | Programmatic initial/restored focus | `FocusRequester` + `Modifier.focusRequester(...)` | | Visual or state reaction to focus changes | `Modifier.onFocusChanged { ... }` | | Custom interactive surface that is not already focusable | `Modifier.focusable()` plus role/semantics as appropriate |
2. Request initial or restored focus from `LaunchedEffect`, keyed to the condition that makes the target present. For lazy content, keep requesters by stable item id and request only after the item is composed. Inside `AnimatedContent`, use the content lambda's target consistently for rendered identity, tags, requester ownership, and the effect key; captured outer state gives outgoing and incoming content the same identity. 3. Keep default spatial search unless a concrete edge, jump, or trap is wrong. Encode only those exceptions with `focusProperties`. 4. Handle keys only for behavior that is not normal click or traversal. Consume exactly the handled event; throttle rapid D-pad work at its expensive owner, not across the screen. 5. Restore by semantic identity after refresh: retain the focused id when it exists, otherwise choose a deterministic fallback. 6. Test with one concrete key/D-pad interaction and focused semantics. When the behavior under review is user-triggered, do not substitute direct state mutation or leave the input conditional. Use screenshots only for the focus appearance. 7. Finish when all intentional targets and exceptional edges are encoded, loading/refresh behavior has a stable focus policy, and tests use the same input model as users.
For example, request and observe focus only when both behaviors are required:
val requester = remember { FocusRequester() }
Button(
onClick = onClick,
modifier = Modifier
.focusRequester(requester)
.onFocusChanged { state -> isFocused = state.isFocused },
) {
Text("Play")
}Call focus requests from an effect, not the composable body:
val initialFocus = remember { FocusRequester() }
LaunchedEffect(initialFocus) {
initialFocus.requestFocus()
}If the target appears after loading, key the request to the condition:
LaunchedEffect(items.isNotEmpty()) {
if (items.isNotEmpty()) {
firstItemRequester.requestFocus()
}
}Use `focusProperties` only when default spatial search is wrong:
Modifier.focusProperties {
up = headerRequester
down = firstRowRequester
left = FocusRequester.Cancel
}Too many hard-coded links create stale focus graphs. For special key behavior, consume only the handled event:
Modifier.onPreviewKeyEvent { event ->
if (event.type == KeyEventType.KeyUp && event.key == Key.Back) {
onBack()
true
} else {
false
}
}Test focus through user input:
composeTestRule.onNodeWithTag("screen").performKeyInput {
pressKey(Key.DirectionDown)
}
composeTestRule.onNodeWithTag("play-button").assertIsFocused()Broader test-shape choices are in [Compose UI testing patterns](../compose-ui-testing-patterns/SKILL.md).
A set of skills for Kotlin, Jetpack Compose, Android development, and grounded writing. The repository is also a portable Agent Plugins and the immediate skill directories under skills/.
Repo: chrisbanes/skills
Use when comparing physical Android benchmark configurations, investigating inconsistent rankings, or selecting an Android default from measured results. Do…
Use when writing or reviewing Jetpack Compose motion: visibility enter/exit, animating one property toward a target, color or size transitions, multiple…
Use when designing or reviewing reusable Jetpack Compose component APIs with modifier parameters, root layout placement, caller-provided variable content,…
Use when investigating Jetpack Compose recomposition cost, compiler stability reports, skippability, unstable parameters, frame-rate State reads, cross-phase…
Use when writing or reviewing Jetpack Compose state ownership, remember state, state hoisting, screen state holders, LaunchedEffect, DisposableEffect,…
Use when writing or reviewing Jetpack Compose UI tests, screenshot tests, previews, semantics assertions, fake image loading, keyboard input, focus assertions,…