Build apps faster with our new App builder! Check it out →

TabRow

Android

Component in Tv Material Compose

TV-Material Design Horizontal TabRow

Display all tabs in a set simultaneously and if the tabs exceed the container size, it has scrolling to navigate to next tab. They are best for switching between related content quickly, such as between transportation methods in a map. To navigate between tabs, use d-pad left or d-pad right when focused.

A TvTabRow contains a row of []s, and displays an indicator underneath the currently selected tab. A TvTabRow places its tabs offset from the starting edge, and allows scrolling to tabs that are placed off screen.

Last updated:

Installation

dependencies {
   implementation("androidx.tv:tv-material:1.0.0")
}

Overloads

@Composable
fun TabRow(
    selectedTabIndex: Int,
    modifier: Modifier = Modifier,
    containerColor: Color = TabRowDefaults.ContainerColor,
    contentColor: Color = TabRowDefaults.contentColor(),
    separator: @Composable () -> Unit = { TabRowDefaults.TabSeparator() },
    indicator: @Composable (tabPositions: List<DpRect>, doesTabRowHaveFocus: Boolean) -> Unit =
        @Composable { tabPositions, doesTabRowHaveFocus ->
            tabPositions.getOrNull(selectedTabIndex)?.let { currentTabPosition ->
                TabRowDefaults.PillIndicator(
                    currentTabPosition = currentTabPosition,
                    doesTabRowHaveFocus = doesTabRowHaveFocus
                )
            }
        },
    tabs: @Composable TabRowScope.() -> Unit
)

Parameters

namedescription
selectedTabIndexthe index of the currently selected tab
modifierthe [Modifier] to be applied to this tab row
containerColorthe color used for the background of this tab row
contentColorthe primary color used in the tabs
separatoruse this composable to add a separator between the tabs
indicatorused to indicate which tab is currently selected and/or focused. This lambda provides 2 values:* tabPositions: list of [DpRect] which provides the position of each tab* doesTabRowHaveFocus: whether any [Tab] within [TabRow] is focused
tabsa composable which will render all the tabs

Code Examples

PillIndicatorTabRow

/** Tab row with a Pill indicator */
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PillIndicatorTabRow() {
    val tabs = listOf("Tab 1", "Tab 2", "Tab 3")
    var selectedTabIndex by remember { mutableStateOf(0) }

    TabRow(selectedTabIndex = selectedTabIndex, modifier = Modifier.focusRestorer()) {
        tabs.forEachIndexed { index, tab ->
            key(index) {
                Tab(
                    selected = index == selectedTabIndex,
                    onFocus = { selectedTabIndex = index },
                ) {
                    Text(
                        text = tab,
                        fontSize = 12.sp,
                        modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)
                    )
                }
            }
        }
    }
}

UnderlinedIndicatorTabRow

/** Tab row with an Underlined indicator */
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun UnderlinedIndicatorTabRow() {
    val tabs = listOf("Tab 1", "Tab 2", "Tab 3")
    var selectedTabIndex by remember { mutableStateOf(0) }

    TabRow(
        selectedTabIndex = selectedTabIndex,
        indicator = { tabPositions, doesTabRowHaveFocus ->
            TabRowDefaults.UnderlinedIndicator(
                currentTabPosition = tabPositions[selectedTabIndex],
                doesTabRowHaveFocus = doesTabRowHaveFocus,
            )
        },
        modifier = Modifier.focusRestorer()
    ) {
        tabs.forEachIndexed { index, tab ->
            key(index) {
                Tab(
                    selected = index == selectedTabIndex,
                    onFocus = { selectedTabIndex = index },
                    colors = TabDefaults.underlinedIndicatorTabColors(),
                ) {
                    Text(
                        text = tab,
                        fontSize = 12.sp,
                        modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)
                    )
                }
            }
        }
    }
}

TabRowWithDebounce

/** Tab row with delay between tab changes */
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun TabRowWithDebounce() {
    val tabs = listOf("Tab 1", "Tab 2", "Tab 3")
    var selectedTabIndex by remember { mutableStateOf(0) }

    // This index will be used to show a panel
    var tabPanelIndex by remember { mutableStateOf(selectedTabIndex) }

    // Change the tab-panel only after some delay
    LaunchedEffect(selectedTabIndex) {
        delay(250.microseconds)
        tabPanelIndex = selectedTabIndex
    }

    TabRow(selectedTabIndex = selectedTabIndex, modifier = Modifier.focusRestorer()) {
        tabs.forEachIndexed { index, tab ->
            key(index) {
                Tab(
                    selected = index == selectedTabIndex,
                    onFocus = { selectedTabIndex = index },
                ) {
                    Text(
                        text = tab,
                        fontSize = 12.sp,
                        modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)
                    )
                }
            }
        }
    }
}

OnClickNavigation

/** Tab changes onClick instead of onFocus */
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun OnClickNavigation() {
    val bgColors =
        listOf(
            Color(0x6a, 0x16, 0x16),
            Color(0x6a, 0x40, 0x16),
            Color(0x6a, 0x6a, 0x16),
            Color(0x40, 0x6a, 0x16),
        )

    var focusedTabIndex by remember { mutableStateOf(0) }
    var activeTabIndex by remember { mutableStateOf(focusedTabIndex) }

    Box(modifier = Modifier.fillMaxSize().background(bgColors[activeTabIndex])) {
        TabRow(
            selectedTabIndex = focusedTabIndex,
            indicator = { tabPositions, doesTabRowHaveFocus ->
                // FocusedTab's indicator
                TabRowDefaults.PillIndicator(
                    currentTabPosition = tabPositions[focusedTabIndex],
                    activeColor = Color.Blue.copy(alpha = 0.4f),
                    inactiveColor = Color.Transparent,
                    doesTabRowHaveFocus = doesTabRowHaveFocus,
                )

                // SelectedTab's indicator
                TabRowDefaults.PillIndicator(
                    currentTabPosition = tabPositions[activeTabIndex],
                    doesTabRowHaveFocus = doesTabRowHaveFocus,
                )
            },
            modifier = Modifier.focusRestorer()
        ) {
            repeat(bgColors.size) {
                key(it) {
                    Tab(
                        selected = activeTabIndex == it,
                        onFocus = { focusedTabIndex = it },
                        onClick = {
                            focusedTabIndex = it
                            activeTabIndex = it
                        }
                    ) {
                        Text(
                            text = "Tab ${it + 1}",
                            fontSize = 12.sp,
                            modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)
                        )
                    }
                }
            }
        }
    }
}
by @alexstyl