# PrimaryTabRow

Common/AllAndroid/JVM

## Functions summary

|---|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `@https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable https://developer.android.com/reference/kotlin/androidx/compose/material3/PrimaryTabRow.composable#PrimaryTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function1,kotlin.Function0,kotlin.Function0)( selectedTabIndex: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html, modifier: https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier, containerColor: https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/Color, contentColor: https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/Color, indicator: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable https://developer.android.com/reference/kotlin/androidx/compose/material3/TabIndicatorScope.() -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html, divider: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable () -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html, tabs: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable () -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html )` [Material Design fixed primary tabs](https://m3.material.io/components/tabs/overview) | Cmn |

## Functions

### PrimaryTabRow

Cmn Artifact: [androidx.compose.material3:material3](https://developer.android.com/jetpack/androidx/releases/compose-material3) [View Source](https://cs.android.com/search?q=file:androidx/compose/material3/TabRow.kt+function:PrimaryTabRow)

```
@Composable
fun PrimaryTabRow(
    selectedTabIndex: Int,
    modifier: Modifier = Modifier,
    containerColor: Color = TabRowDefaults.primaryContainerColor,
    contentColor: Color = TabRowDefaults.primaryContentColor,
    indicator: @Composable TabIndicatorScope.() -> Unit = {
        TabRowDefaults.PrimaryIndicator(
            modifier = Modifier.tabIndicatorOffset(selectedTabIndex, matchContentSize = true),
            width = Dp.Unspecified,
        )
    },
    divider: @Composable () -> Unit = @Composable { HorizontalDivider() },
    tabs: @Composable () -> Unit
): Unit
```

[Material Design fixed primary tabs](https://m3.material.io/components/tabs/overview)

Primary tabs are placed at the top of the content pane under a top app bar. They display the main content destinations. Fixed tabs display all tabs in a set simultaneously. They are best for switching between related content quickly, such as between transportation methods in a map. To navigate between fixed tabs, tap an individual tab, or swipe left or right in the content area.

A TabRow contains a row of `https://developer.android.com/reference/kotlin/androidx/compose/material3/Tab.composable#Tab(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,kotlin.Boolean,kotlin.Function0,kotlin.Function0,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.foundation.interaction.MutableInteractionSource)`s, and displays an indicator underneath the currently selected tab. A TabRow places its tabs evenly spaced along the entire row, with each tab taking up an equal amount of space. See `https://developer.android.com/reference/kotlin/androidx/compose/material3/PrimaryScrollableTabRow.composable#PrimaryScrollableTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.foundation.ScrollState,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,kotlin.Function1,kotlin.Function0,androidx.compose.ui.unit.Dp,kotlin.Function0)` for a tab row that does not enforce equal size, and allows scrolling to tabs that do not fit on screen.

A simple example with text tabs looks like:

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow

var state by rememberSaveable { mutableStateOf(0) }
val titles = listOf("Tab 1", "Tab 2", "Tab 3 with lots of text")
Column {
    PrimaryTabRow(selectedTabIndex = state) {
        titles.forEachIndexed { index, title ->
            Tab(
                selected = state == index,
                onClick = { state = index },
                text = { Text(text = title, maxLines = 2, overflow = TextOverflow.Ellipsis) },
            )
        }
    }
    Text(
        modifier = Modifier.align(Alignment.CenterHorizontally),
        text = "Primary tab ${state + 1} selected",
        style = MaterialTheme.typography.bodyLarge,
    )
}
```

You can also provide your own custom tab, such as:

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SecondaryTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

var state by rememberSaveable { mutableStateOf(0) }
val titles = listOf("Tab 1", "Tab 2", "Tab 3")
Column {
    SecondaryTabRow(selectedTabIndex = state) {
        titles.forEachIndexed { index, title ->
            FancyTab(title = title, onClick = { state = index }, selected = (index == state))
        }
    }
    Text(
        modifier = Modifier.align(Alignment.CenterHorizontally),
        text = "Fancy tab ${state + 1} selected",
        style = MaterialTheme.typography.bodyLarge,
    )
}
```

Where the custom tab itself could look like:

```kotlin
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

Tab(selected, onClick) {
    Column(
        Modifier.padding(10.dp).height(50.dp).fillMaxWidth(),
        verticalArrangement = Arrangement.SpaceBetween,
    ) {
        Box(
            Modifier.size(10.dp)
                .align(Alignment.CenterHorizontally)
                .background(
                    color =
                        if (selected) MaterialTheme.colorScheme.primary
                        else MaterialTheme.colorScheme.background
                )
        )
        Text(
            text = title,
            style = MaterialTheme.typography.bodyLarge,
            modifier = Modifier.align(Alignment.CenterHorizontally),
        )
    }
}
```

As well as customizing the tab, you can also provide a custom `https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#PrimaryTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function1,kotlin.Function0,kotlin.Function0)`, to customize the indicator displayed for a tab. `https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#PrimaryTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function1,kotlin.Function0,kotlin.Function0)` will be placed to fill the entire TabRow, so it should internally take care of sizing and positioning the indicator to match changes to `https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#PrimaryTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function1,kotlin.Function0,kotlin.Function0)`.

For example, given an indicator that draws a rounded rectangle near the edges of the `https://developer.android.com/reference/kotlin/androidx/compose/material3/Tab.composable#Tab(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,kotlin.Boolean,kotlin.Function0,kotlin.Function0,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.foundation.interaction.MutableInteractionSource)`:

```kotlin
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Tab
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.dp

// Draws a rounded rectangular with border around the Tab, with a 5.dp padding from the edges
// Color is passed in as a parameter [color]
Box(
    modifier
        .padding(5.dp)
        .fillMaxSize()
        .border(BorderStroke(2.dp, color), RoundedCornerShape(5.dp))
)
```

We can reuse `https://developer.android.com/reference/kotlin/androidx/compose/material3/TabRowDefaults#(androidx.compose.ui.Modifier).tabIndicatorOffset(androidx.compose.material3.TabPosition)` and just provide this indicator, as we aren't changing how the size and position of the indicator changes between tabs:

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SecondaryTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

var state by rememberSaveable { mutableStateOf(0) }
val titles = listOf("Tab 1", "Tab 2", "Tab 3")

Column {
    SecondaryTabRow(
        selectedTabIndex = state,
        indicator = {
            FancyIndicator(
                MaterialTheme.colorScheme.primary,
                Modifier.tabIndicatorOffset(state),
            )
        },
    ) {
        titles.forEachIndexed { index, title ->
            Tab(selected = state == index, onClick = { state = index }, text = { Text(title) })
        }
    }
    Text(
        modifier = Modifier.align(Alignment.CenterHorizontally),
        text = "Fancy indicator tab ${state + 1} selected",
        style = MaterialTheme.typography.bodyLarge,
    )
}
```

You may also want to use a custom transition, to allow you to dynamically change the appearance of the indicator as it animates between tabs, such as changing its color or size. `https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#PrimaryTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function1,kotlin.Function0,kotlin.Function0)` is stacked on top of the entire TabRow, so you just need to provide a custom transition that animates the offset of the indicator from the start of the TabRow. For example, take the following example that uses a transition to animate the offset, width, and color of the same FancyIndicator from before, also adding a physics based 'spring' effect to the indicator in the direction of motion:

```kotlin
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab
import androidx.compose.material3.TabPosition
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

val colors =
    listOf(
        MaterialTheme.colorScheme.primary,
        MaterialTheme.colorScheme.secondary,
        MaterialTheme.colorScheme.tertiary,
    )
var startAnimatable by remember { mutableStateOf<Animatable<Dp, AnimationVector1D>?>(null) }
var endAnimatable by remember { mutableStateOf<Animatable<Dp, AnimationVector1D>?>(null) }
val coroutineScope = rememberCoroutineScope()
val indicatorColor: Color by animateColorAsState(colors[index % colors.size], label = "")

Box(
    Modifier.tabIndicatorLayout {
            measurable: Measurable,
            constraints: Constraints,
            tabPositions: List<TabPosition> ->
            val newStart = tabPositions[index].left
            val newEnd = tabPositions[index].right
            val startAnim =
                startAnimatable
                    ?: Animatable(newStart, Dp.VectorConverter).also { startAnimatable = it }

            val endAnim =
                endAnimatable
                    ?: Animatable(newEnd, Dp.VectorConverter).also { endAnimatable = it }

            if (endAnim.targetValue != newEnd) {
                coroutineScope.launch {
                    endAnim.animateTo(
                        newEnd,
                        animationSpec =
                            if (endAnim.targetValue < newEnd) {
                                spring(dampingRatio = 1f, stiffness = 1000f)
                            } else {
                                spring(dampingRatio = 1f, stiffness = 50f)
                            },
                    )
                }
            }

            if (startAnim.targetValue != newStart) {
                coroutineScope.launch {
                    startAnim.animateTo(
                        newStart,
                        animationSpec =
                            // Handle directionality here, if we are moving to the right, we
                            // want the right side of the indicator to move faster, if we are
                            // moving to the left, we want the left side to move faster.
                            if (startAnim.targetValue < newStart) {
                                spring(dampingRatio = 1f, stiffness = 50f)
                            } else {
                                spring(dampingRatio = 1f, stiffness = 1000f)
                            },
                    )
                }
            }

            val indicatorEnd = endAnim.value.roundToPx()
            val indicatorStart = startAnim.value.roundToPx()

            // Apply an offset from the start to correctly position the indicator around the tab
            val placeable =
                measurable.measure(
                    constraints.copy(
                        maxWidth = indicatorEnd - indicatorStart,
                        minWidth = indicatorEnd - indicatorStart,
                    )
                )
            layout(constraints.maxWidth, constraints.maxHeight) {
                val contentWidth = tabPositions[index].contentWidth.roundToPx()
                val tabWidth = tabPositions[index].width.roundToPx()
                val relativeOffset = if (isScrollable) (tabWidth - contentWidth) / 2 else 0
                placeable.place(indicatorStart - relativeOffset, 0)
            }
        }
        .padding(5.dp)
        .fillMaxSize()
        .drawWithContent {
            drawRoundRect(
                color = indicatorColor,
                cornerRadius = CornerRadius(5.dp.toPx()),
                style = Stroke(width = 2.dp.toPx()),
            )
        }
)
```

We can now just pass this indicator directly to TabRow:

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SecondaryTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

var state by rememberSaveable { mutableStateOf(0) }
val titles = listOf("Tab 1", "Tab 2", "Tab 3")

Column {
    SecondaryTabRow(
        selectedTabIndex = state,
        indicator = { FancyAnimatedIndicatorWithModifier(state) },
    ) {
        titles.forEachIndexed { index, title ->
            Tab(selected = state == index, onClick = { state = index }, text = { Text(title) })
        }
    }
    Text(
        modifier = Modifier.align(Alignment.CenterHorizontally),
        text = "Fancy transition tab ${state + 1} selected",
        style = MaterialTheme.typography.bodyLarge,
    )
}
```

| Parameters |
|---|---|
| `selectedTabIndex: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html` | the index of the currently selected tab |
| `modifier: https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier = Modifier` | the `https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier` to be applied to this tab row |
| `containerColor: https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/Color = TabRowDefaults.primaryContainerColor` | the color used for the background of this tab row. Use `https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/Color#Transparent()` to have no color. |
| `contentColor: https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/Color = TabRowDefaults.primaryContentColor` | the preferred color for content inside this tab row. Defaults to either the matching content color for `https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#PrimaryTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function1,kotlin.Function0,kotlin.Function0)`, or to the current `https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#LocalContentColor()` if `https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#PrimaryTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function1,kotlin.Function0,kotlin.Function0)` is not a color from the theme. |
| `indicator: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable https://developer.android.com/reference/kotlin/androidx/compose/material3/TabIndicatorScope.() -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html = { TabRowDefaults.PrimaryIndicator( modifier = Modifier.tabIndicatorOffset(selectedTabIndex, matchContentSize = true), width = Dp.Unspecified, ) }` | the indicator that represents which tab is currently selected. By default this will be a `https://developer.android.com/reference/kotlin/androidx/compose/material3/TabRowDefaults#PrimaryIndicator(androidx.compose.ui.Modifier,androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Shape)`, using a `https://developer.android.com/reference/kotlin/androidx/compose/material3/TabRowDefaults#(androidx.compose.ui.Modifier).tabIndicatorOffset(androidx.compose.material3.TabPosition)` modifier to animate its position. |
| `divider: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable () -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html = @Composable { HorizontalDivider() }` | the divider displayed at the bottom of the tab row. This provides a layer of separation between the tab row and the content displayed underneath. |
| `tabs: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable () -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | the tabs inside this tab row. Typically this will be multiple `https://developer.android.com/reference/kotlin/androidx/compose/material3/Tab.composable#Tab(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,kotlin.Boolean,kotlin.Function0,kotlin.Function0,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.foundation.interaction.MutableInteractionSource)`s. Each element inside this lambda will be measured and placed evenly across the row, each taking up equal space. |