For TV apps, the browsing experience relies on efficient focus-based navigation.
Using standard Compose Foundation lazy layouts, you can create performant
vertical and horizontal lists that automatically handle focus-driven scrolling
to keep active items in view.

## Default scroll behavior optimized for TV

Starting with Compose Foundation 1.7.0, standard lazy layouts (like `LazyRow`
and `LazyColumn`) include built-in support for focus-positioning features. This
is the recommended way to build catalogs for TV apps as it helps keep focused
items remain visible and positioned intuitively for the user.

To implement a basic scrollable list, use the standard lazy components. These
components automatically handle D-pad navigation and bring the focused item into
view.


```kotlin
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items

@Composable
fun MovieCatalog(movies: List<Movie>) {
    LazyRow {
        items(movies) { movie ->
            MovieCard(
                movie = movie,
                onClick = { /* Handle click */ }
            )
        }
    }
}
```

<br />

## Customize scroll behavior with `BringIntoViewSpec`

If your design requires a specific "pivot" point (for example, keeping the
focused item exactly 30% from the left edge), you can customize the scrolling
behavior using a `BringIntoViewSpec`. This replaces the older `pivotOffsets`
functionality by allowing you to define exactly how the viewport should scroll
to accommodate a focused item.

### 1. Define a custom `BringIntoViewSpec`

The following helper composable lets you define a "pivot" based on parent and
child fractions. The `parentFraction` determines where in the container the item
should land, and the `childFraction` determines which part of the item aligns
with that point.


```kotlin
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PositionFocusedItemInLazyLayout(
    parentFraction: Float = 0.3f,
    childFraction: Float = 0f,
    content: @Composable () -> Unit,
) {
    val bringIntoViewSpec = remember(parentFraction, childFraction) {
        object : BringIntoViewSpec {
            override fun calculateScrollDistance(
                offset: Float,       // Item's initial position
                size: Float,         // Item's size
                containerSize: Float // Container's size
            ): Float {
                // Calculate the offset position of the item's leading edge.
                val initialTargetForLeadingEdge =
                    parentFraction * containerSize - (childFraction * size)
                // If the item fits in the container, and scrolling would cause
                // its trailing edge to be clipped, adjust targetForLeadingEdge
                // to prevent over-scrolling near the end of list.
                val targetForLeadingEdge = if (size <= containerSize &&
                    (containerSize - initialTargetForLeadingEdge) < size) {
                    // If clipped, align the item's trailing edge with the
                    // container's trailing edge.
                    containerSize - size
                } else {
                    initialTargetForLeadingEdge
                }
                // Return scroll distance relative to initial item position.
                return offset - targetForLeadingEdge
            }
        }
    }

    // Apply the spec to all scrollables in the hierarchy
    CompositionLocalProvider(
        LocalBringIntoViewSpec provides bringIntoViewSpec,
        content = content,
    )
}
```

<br />

### 2. Apply the custom spec

Wrap your layouts with the helper to apply the positioning. This is useful for
creating a "consistent focus line" across different rows of your catalog.


```kotlin
PositionFocusedItemInLazyLayout(
    parentFraction = 0.3f, // Pivot 30% from the edge
    childFraction = 0.5f   // Center of the item aligns with the pivot
) {
    LazyColumn {
        items(sectionList) { section ->
            // This row and its items will respect the 30% pivot
            LazyRow { /* ... */ }
        }
    }
}
```

<br />

### 3. Opt-out for specific nested layouts

If you have a specific nested layout that should use standard scrolling behavior
instead of your custom pivot, provide the `DefaultBringIntoViewSpec`:


```kotlin
private val DefaultBringIntoViewSpec = object : BringIntoViewSpec {}

PositionFocusedItemInLazyLayout {
    LazyColumn {
        item {
            // This row will ignore the custom pivot and use default behavior
            CompositionLocalProvider(LocalBringIntoViewSpec provides DefaultBringIntoViewSpec) {
                LazyRow { /* ... */ }
            }
        }
    }
}
```

<br />

In effect, by passing an empty `BringIntoViewSpec` enables the framework's
default behavior to take over.

## Migration from TV Foundation to Compose Foundation

The TV-specific lazy layouts in `androidx.tv.foundation` are deprecated in favor
of the standard Compose Foundation layouts.

### Dependency updates

Verify that your `build.gradle` uses version 1.7.0 or higher for:

- `androidx.compose.foundation`
- `androidx.compose.runtime`

### Component mapping

To migrate, update your imports and remove the `Tv` prefix from your components:

| Deprecated TV component | Compose Foundation replacement |
|---|---|
| TvLazyRow | LazyRow |
| TvLazyColumn | LazyColumn |
| TvLazyHorizontalGrid | LazyHorizontalGrid |
| TvLazyVerticalGrid | LazyVerticalGrid |
| pivotOffsets | BringIntoViewSpec (via LocalBringIntoViewSpec) |