New Compose Multiplatform components arrived on Composables UICheck it out →

TabRow

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-beta01")
}

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

@Sampled
@Composable
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class
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

@Sampled
@Composable
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class
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

@Sampled
@Composable
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class
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

@Sampled
@Composable
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class
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)
            )
          }
        }
      }
    }
  }
}