Skip to main content
skill CX Codex

compose-focus-navigation — Claude Code skill

This skill guides focus navigation in Jetpack Compose for TV, keyboards, desktop, accessibility, and D-pad input. It helps with initial and restored focus.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add chrisbanes/skills --skill compose-focus-navigation -a claude-code
Open on GitHub

What compose-focus-navigation does

The skill treats focus as stateful UI behavior. It keeps ordinary focusable components simple, then adds FocusRequester, focusProperties, onFocusChanged, or focusable() for specific needs. Initial and restored focus requests belong in LaunchedEffect; lazy content should request focus only after its target is composed and should retain stable item identity.

It also covers spatial-search exceptions, key handling, refresh behavior, and focused semantics. Use focusProperties for real jumps or traps rather than hard-coding the whole focus graph. Key handlers should consume only the event they handle, while tests should drive input with performKeyInput and verify assertIsFocused(). If a project does not use Jetpack Compose, these recommendations do not apply.

What it helps with

  • set initial focus after content loads
  • restore focus after a data refresh
  • define D-pad focus exceptions
  • handle a special key event
  • test focus through user input

Example prompts for the agent

  • Review this Jetpack Compose TV screen and make initial focus request from a keyed LaunchedEffect after the target is present.
  • Use focusProperties to connect the header to the first row while keeping default spatial search elsewhere.
  • Write a test that presses Key.DirectionDown with performKeyInput and checks assertIsFocused() on play-button.

How to install compose-focus-navigation

1Skills CLI (Vercel)

npx skills add chrisbanes/skills --skill compose-focus-navigation -a claude-code

2Manually: put the files in place

~/.claude/skills/compose-focus-navigation/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/compose-focus-navigation/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about compose-focus-navigation

How do I install compose-focus-navigation in Claude Code?

Run npx skills add chrisbanes/skills --skill compose-focus-navigation -a claude-code, then run the same command again: npx skills add chrisbanes/skills --skill compose-focus-navigation -a claude-code. The skill file is at ~/.claude/skills/compose-focus-navigation/SKILL.md.

What does compose-focus-navigation do?

It guides Jetpack Compose focus behavior for initial and restored focus, D-pad navigation, key events, spatial-search exceptions, and input-based tests.

Which platforms support compose-focus-navigation?

The skill is available for Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, and OpenCode.

Source text

SKILL.md · Apache-2.0
---
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.
---

# Compose: focus navigation

## Core principle

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.

## Procedure

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.
   In a review, a finding about `requestFocus()` running in the composable body
   is incomplete until it names the event or keyed effect that should issue
   the request after its target is available.
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:

```kotlin
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:

```kotlin
val initialFocus = remember { FocusRequester() }

LaunchedEffect(initialFocus) {
    initialFocus.requestFocus()
}
```

If the target appears after loading, key the request to the condition:

```kotlin
LaunchedEffect(items.isNotEmpty()) {
    if (items.isNotEmpty()) {
        firstItemRequester.requestFocus()
    }
}
```

Use `focusProperties` only when default spatial search is wrong:

```kotlin
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:

```kotlin
Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Back) {
        onBack()
        true
    } else {
        false
    }
}
```

Test focus through user input:

```kotlin
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).

© chrisbanes · Apache-2.0 · chrisbanes/skills

Similar skills

Everything for Frontend and UI →

From the same set18

compose-ui-testing-patterns Guides Jetpack Compose UI testing for semantics, screenshots, keyboard input, focus, and interaction state. CX Codex +1 2,200installs compose-animations Helps write and review Jetpack Compose motion across common UI states. Useful when picking between AnimatedVisibility, AnimatedContent, animate*AsState, and rememberTransition. CX Codex +1 1,800installs shepherd Monitors open pull requests and merge requests, triages review feedback, addresses CI failures, and batches local fixes. CX Codex +1 1,200installs kotlin-concurrency-and-flow A skill for writing and reviewing Kotlin coroutine code: scope ownership, cancellation, StateFlow, SharedFlow and Channel. CX Codex +1 1,100installs compose-performance A skill for investigating Jetpack Compose recomposition cost: find the phase causing the work and fix it at the boundary where it starts. CX Codex +1 1,100installs compose-state-and-effects This skill guides state ownership and side-effect choices in Jetpack Compose. It keeps UI state at the lowest responsible layer and matches effect lifecycles to their owners. CX Codex +1 1,100installs using-chrisbanes-skills Reads a Kotlin or Jetpack Compose task and loads the right skill from the chrisbanes set. Useful when several concerns overlap in one change. CX Codex +1 1,100installs kotlin-control-flow A skill that steers your coding agent through Kotlin branching: when expressions, guard conditions, sealed-type exhaustiveness, smart casts. CX Codex +1 1,100installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor