RokuFocus · Android TV & Fire TV

Focus, fixed.

Roku-style fixed-focus D-pad navigation, in pure Jetpack Compose. The highlight stays locked. The content glides beneath it.

Get started Try it live
v1.0.0 on JitPack Apache 2.0 minSdk 24 0 Material deps
Scroll

Android TV moves focus to every item. Roku never does. The highlight stays locked. The content glides. RokuFocus brings that to Compose.

For YouMoviesSeriesLive
Continue watching
Trending now
The rail moves. The ring doesn't.One focusable container — items are never individually focused.
Hold right. It accelerates.Key-repeat throttling that speeds up after three presses.
A highlight on the GPU.Positioned with graphicsLayer — translation only, no re-layout.

Two focus models. One feels right.

Compose TV's default moves focus to each item, so the whole row jumps around. RokuFocus flips it — the same pattern Roku and every major streaming app ships.

Default Compose TV

Focus chases items across the screen, then the list lurches to catch up. Your eye has to track two moving things.

RokuFocus

The highlight never moves. Content glides under a fixed anchor — predictable, cinematic, easy to track from ten feet away.

Don't take our word. Take the remote.

A live RokuFocus row, wired to your keyboard. Every control below maps to a real RokuFocusConfig parameter.

Up next
state.selectedIndex = 0
highlightAnimationSpec
wrapAround
focusSlot

Click the demo, then press to navigate · Enter to select. Hit an edge with wrap off to feel the boundary.

Everything Leanback should have been. In Compose.

No Views, no fragments, no fighting the focus engine. Drop it in and ship.

Fixed-focus highlight

Content scrolls under a stationary ring — selection is tracked in state, not the Compose focus system.

Full OTT grids

RokuLazyColumn morphs one highlight across rows of different card sizes — X, Y, width, and height all animate.

Key-repeat acceleration

Hold the D-pad: presses are throttled, then accelerate after three in a row. Long lists stop feeling long.

Wrap-around + haptics

Last to first with one flag. Boundary hits vibrate, so the edge is something users feel, not guess.

Custom highlight

The ring is a BoxScope lambda. Keep the default, restyle it, or replace it with anything you can compose.

Zero Material

Only Foundation, UI, Animation, and Runtime. Works with any design system, back to minSdk 24.

An API that reads like the layout it builds.

Rows, grids, and config — straight from the library.

HomeScreen.kt
// A fixed-focus row. Item width is auto-measured.
RokuLazyRow(
    itemSpacing = 14.dp,
    onItemClicked = { index -> play(index) }
) {
    items(movies) { movie, isFocused ->
        MovieCard(movie, isFocused)
    }
}
// A full OTT home screen: mixed card sizes, one morphing highlight.
RokuLazyColumn {
    row(itemWidth = 580.dp, itemHeight = 310.dp,
        header = { Text("Hero") }) {
        items(heroMovies) { m, focused -> BannerCard(m, focused) }
    }
    row(itemWidth = 220.dp, itemHeight = 140.dp,
        header = { Text("Trending Now") }) {
        items(trending) { m, focused -> MovieCard(m, focused) }
    }
}
// Tune the feel — globally or per component.
val config = RokuFocusConfig(
    highlightAnimationSpec = RokuAnimationSpec.Smooth,
    keyRepeatDelayMs = 150L,
    keyRepeatAccelAfter = 3,   // speed up after 3 presses
    keyRepeatFastDelayMs = 50L,
    wrapAround = true,
    hapticFeedback = true
)
0min SDK
0Kotlin
0Material deps
0focusable per row

Install in seconds.

Add JitPack, add the dependency, press right.

settings.gradle.kts · build.gradle.kts
// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

// build.gradle.kts
dependencies {
    implementation("com.github.souravnoobcoder:roku-focus-list:1.0.0")
}

Make the remote
feel right.

Open source. Apache 2.0. Built for the ten-foot experience.

View on GitHub Back to portfolio