Merge "[Tabs] Add primary/secondary indicators" into androidx-main
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 1f5b379..b6e6d45 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -758,16 +758,20 @@
   }
 
   @androidx.compose.runtime.Immutable public final class TabPosition {
+    method public float getContentWidth();
     method public float getLeft();
     method public float getRight();
     method public float getWidth();
+    property public final float contentWidth;
     property public final float left;
     property public final float right;
     property public final float width;
   }
 
   public final class TabRowDefaults {
-    method @androidx.compose.runtime.Composable public void Indicator(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method @Deprecated @androidx.compose.runtime.Composable public void Indicator(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method @androidx.compose.runtime.Composable public void PrimaryIndicator(optional androidx.compose.ui.Modifier modifier, optional float width, optional float height, optional long color, optional androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Composable public void SecondaryIndicator(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method @androidx.compose.runtime.Composable public long getContentColor();
     method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material3.TabPosition currentTabPosition);
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index b2471cb..a2231ee 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -1145,16 +1145,20 @@
   }
 
   @androidx.compose.runtime.Immutable public final class TabPosition {
+    method public float getContentWidth();
     method public float getLeft();
     method public float getRight();
     method public float getWidth();
+    property public final float contentWidth;
     property public final float left;
     property public final float right;
     property public final float width;
   }
 
   public final class TabRowDefaults {
-    method @androidx.compose.runtime.Composable public void Indicator(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method @Deprecated @androidx.compose.runtime.Composable public void Indicator(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method @androidx.compose.runtime.Composable public void PrimaryIndicator(optional androidx.compose.ui.Modifier modifier, optional float width, optional float height, optional long color, optional androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Composable public void SecondaryIndicator(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method @androidx.compose.runtime.Composable public long getContentColor();
     method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material3.TabPosition currentTabPosition);
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 1f5b379..b6e6d45 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -758,16 +758,20 @@
   }
 
   @androidx.compose.runtime.Immutable public final class TabPosition {
+    method public float getContentWidth();
     method public float getLeft();
     method public float getRight();
     method public float getWidth();
+    property public final float contentWidth;
     property public final float left;
     property public final float right;
     property public final float width;
   }
 
   public final class TabRowDefaults {
-    method @androidx.compose.runtime.Composable public void Indicator(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method @Deprecated @androidx.compose.runtime.Composable public void Indicator(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method @androidx.compose.runtime.Composable public void PrimaryIndicator(optional androidx.compose.ui.Modifier modifier, optional float width, optional float height, optional long color, optional androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Composable public void SecondaryIndicator(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method @androidx.compose.runtime.Composable public long getContentColor();
     method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material3.TabPosition currentTabPosition);
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index 1d6480b..aef2f9c 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -101,6 +101,7 @@
 import androidx.compose.material3.samples.PinnedTopAppBar
 import androidx.compose.material3.samples.PlainTooltipSample
 import androidx.compose.material3.samples.PlainTooltipWithManualInvocationSample
+import androidx.compose.material3.samples.PrimaryTabs
 import androidx.compose.material3.samples.RadioButtonSample
 import androidx.compose.material3.samples.RadioGroupSample
 import androidx.compose.material3.samples.RangeSliderSample
@@ -113,8 +114,11 @@
 import androidx.compose.material3.samples.ScaffoldWithMultilineSnackbar
 import androidx.compose.material3.samples.ScaffoldWithSimpleSnackbar
 import androidx.compose.material3.samples.ScrollingFancyIndicatorContainerTabs
+import androidx.compose.material3.samples.ScrollingPrimaryTabs
+import androidx.compose.material3.samples.ScrollingSecondaryTabs
 import androidx.compose.material3.samples.ScrollingTextTabs
 import androidx.compose.material3.samples.SearchBarSample
+import androidx.compose.material3.samples.SecondaryTabs
 import androidx.compose.material3.samples.SimpleBottomAppBar
 import androidx.compose.material3.samples.SimpleBottomSheetScaffoldSample
 import androidx.compose.material3.samples.SimpleCenterAlignedTopAppBar
@@ -894,6 +898,20 @@
 private const val TabsExampleSourceUrl = "$SampleSourceUrl/TabSamples.kt"
 val TabsExamples = listOf(
     Example(
+        name = ::PrimaryTabs.name,
+        description = TabsExampleDescription,
+        sourceUrl = TabsExampleSourceUrl
+    ) {
+        PrimaryTabs()
+    },
+    Example(
+        name = ::SecondaryTabs.name,
+        description = TabsExampleDescription,
+        sourceUrl = TabsExampleSourceUrl
+    ) {
+        SecondaryTabs()
+    },
+    Example(
         name = ::TextTabs.name,
         description = TabsExampleDescription,
         sourceUrl = TabsExampleSourceUrl
@@ -922,6 +940,20 @@
         LeadingIconTabs()
     },
     Example(
+        name = ::ScrollingPrimaryTabs.name,
+        description = TabsExampleDescription,
+        sourceUrl = TabsExampleSourceUrl
+    ) {
+        ScrollingPrimaryTabs()
+    },
+    Example(
+        name = ::ScrollingSecondaryTabs.name,
+        description = TabsExampleDescription,
+        sourceUrl = TabsExampleSourceUrl
+    ) {
+        ScrollingSecondaryTabs()
+    },
+    Example(
         name = ::ScrollingTextTabs.name,
         description = TabsExampleDescription,
         sourceUrl = TabsExampleSourceUrl
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt
index bbeb8b3..c19a692 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.Sampled
 import androidx.compose.animation.animateColor
 import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.BorderStroke
@@ -29,24 +30,25 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material3.Icon
+import androidx.compose.material3.LeadingIconTab
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.ScrollableTabRow
 import androidx.compose.material3.Tab
-import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
 import androidx.compose.material3.TabPosition
 import androidx.compose.material3.TabRow
-import androidx.compose.material3.LeadingIconTab
+import androidx.compose.material3.TabRowDefaults
+import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
 import androidx.compose.material3.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -59,6 +61,58 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 
+@Composable
+fun PrimaryTabs() {
+    var state by remember { mutableStateOf(0) }
+    val titles = listOf("Tab 1", "Tab 2", "Tab 3 with lots of text")
+    Column {
+        TabRow(selectedTabIndex = state, indicator = @Composable { tabPositions ->
+            if (state < tabPositions.size) {
+                val width by animateDpAsState(targetValue = tabPositions[state].contentWidth)
+                TabRowDefaults.PrimaryIndicator(
+                    modifier = Modifier.tabIndicatorOffset(tabPositions[state]),
+                    width = width
+                )
+            }
+        }) {
+            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
+        )
+    }
+}
+
+@Composable
+fun SecondaryTabs() {
+    var state by remember { mutableStateOf(0) }
+    val titles = listOf("Tab 1", "Tab 2", "Tab 3 with lots of text")
+    Column {
+        TabRow(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 = "Secondary tab ${state + 1} selected",
+            style = MaterialTheme.typography.bodyLarge
+        )
+    }
+}
+
 @Preview
 @Sampled
 @Composable
@@ -160,6 +214,80 @@
 }
 
 @Composable
+fun ScrollingPrimaryTabs() {
+    var state by remember { mutableStateOf(0) }
+    val titles = listOf(
+        "Tab 1",
+        "Tab 2",
+        "Tab 3 with lots of text",
+        "Tab 4",
+        "Tab 5",
+        "Tab 6 with lots of text",
+        "Tab 7",
+        "Tab 8",
+        "Tab 9 with lots of text",
+        "Tab 10"
+    )
+    Column {
+        ScrollableTabRow(selectedTabIndex = state, indicator = @Composable { tabPositions ->
+            if (state < tabPositions.size) {
+                val width by animateDpAsState(targetValue = tabPositions[state].contentWidth)
+                TabRowDefaults.PrimaryIndicator(
+                    modifier = Modifier.tabIndicatorOffset(tabPositions[state]),
+                    width = width
+                )
+            }
+        }) {
+            titles.forEachIndexed { index, title ->
+                Tab(
+                    selected = state == index,
+                    onClick = { state = index },
+                    text = { Text(title) }
+                )
+            }
+        }
+        Text(
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            text = "Scrolling primary tab ${state + 1} selected",
+            style = MaterialTheme.typography.bodyLarge
+        )
+    }
+}
+
+@Composable
+fun ScrollingSecondaryTabs() {
+    var state by remember { mutableStateOf(0) }
+    val titles = listOf(
+        "Tab 1",
+        "Tab 2",
+        "Tab 3 with lots of text",
+        "Tab 4",
+        "Tab 5",
+        "Tab 6 with lots of text",
+        "Tab 7",
+        "Tab 8",
+        "Tab 9 with lots of text",
+        "Tab 10"
+    )
+    Column {
+        ScrollableTabRow(selectedTabIndex = state) {
+            titles.forEachIndexed { index, title ->
+                Tab(
+                    selected = state == index,
+                    onClick = { state = index },
+                    text = { Text(title) }
+                )
+            }
+        }
+        Text(
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            text = "Scrolling secondary tab ${state + 1} selected",
+            style = MaterialTheme.typography.bodyLarge
+        )
+    }
+}
+
+@Composable
 fun ScrollingTextTabs() {
     var state by remember { mutableStateOf(0) }
     val titles = listOf(
@@ -201,7 +329,11 @@
     Column {
         TabRow(selectedTabIndex = state) {
             titles.forEachIndexed { index, title ->
-                FancyTab(title = title, onClick = { state = index }, selected = (index == state))
+                FancyTab(
+                    title = title,
+                    onClick = { state = index },
+                    selected = (index == state)
+                )
             }
         }
         Text(
@@ -334,7 +466,8 @@
                     .align(Alignment.CenterHorizontally)
                     .background(
                         color = if (selected) MaterialTheme.colorScheme.primary
-                        else MaterialTheme.colorScheme.background)
+                        else MaterialTheme.colorScheme.background
+                    )
             )
             Text(
                 text = title,
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TabScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TabScreenshotTest.kt
index cced64f..318b5c0 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TabScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TabScreenshotTest.kt
@@ -59,7 +59,7 @@
     val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
 
     @Test
-    fun lightTheme() {
+    fun lightTheme_primary() {
         val interactionSource = MutableInteractionSource()
 
         var scope: CoroutineScope? = null
@@ -67,7 +67,7 @@
         composeTestRule.setContent {
             scope = rememberCoroutineScope()
             MaterialTheme(lightColorScheme()) {
-                DefaultTabs(interactionSource)
+                DefaultPrimaryTabs(interactionSource)
             }
         }
 
@@ -75,12 +75,12 @@
             scope = scope!!,
             interactionSource = interactionSource,
             interaction = null,
-            goldenIdentifier = "tabs_lightTheme"
+            goldenIdentifier = "tabs_lightTheme_primary"
         )
     }
 
     @Test
-    fun lightTheme_pressed() {
+    fun lightTheme_secondary() {
         val interactionSource = MutableInteractionSource()
 
         var scope: CoroutineScope? = null
@@ -88,7 +88,28 @@
         composeTestRule.setContent {
             scope = rememberCoroutineScope()
             MaterialTheme(lightColorScheme()) {
-                DefaultTabs(interactionSource)
+                DefaultSecondaryTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "tabs_lightTheme_secondary"
+        )
+    }
+
+    @Test
+    fun lightTheme_primary_pressed() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(lightColorScheme()) {
+                DefaultPrimaryTabs(interactionSource)
             }
         }
 
@@ -96,54 +117,12 @@
             scope = scope!!,
             interactionSource = interactionSource,
             interaction = PressInteraction.Press(Offset(10f, 10f)),
-            goldenIdentifier = "tabs_lightTheme_pressed"
+            goldenIdentifier = "tabs_lightTheme_primary_pressed"
         )
     }
 
     @Test
-    fun darkTheme() {
-        val interactionSource = MutableInteractionSource()
-
-        var scope: CoroutineScope? = null
-
-        composeTestRule.setContent {
-            scope = rememberCoroutineScope()
-            MaterialTheme(darkColorScheme()) {
-                DefaultTabs(interactionSource)
-            }
-        }
-
-        assertTabsMatch(
-            scope = scope!!,
-            interactionSource = interactionSource,
-            interaction = null,
-            goldenIdentifier = "tabs_darkTheme"
-        )
-    }
-
-    @Test
-    fun darkTheme_pressed() {
-        val interactionSource = MutableInteractionSource()
-
-        var scope: CoroutineScope? = null
-
-        composeTestRule.setContent {
-            scope = rememberCoroutineScope()
-            MaterialTheme(darkColorScheme()) {
-                DefaultTabs(interactionSource)
-            }
-        }
-
-        assertTabsMatch(
-            scope = scope!!,
-            interactionSource = interactionSource,
-            interaction = PressInteraction.Press(Offset(10f, 10f)),
-            goldenIdentifier = "tabs_darkTheme_pressed"
-        )
-    }
-
-    @Test
-    fun leadingIconTabs_lightTheme() {
+    fun lightTheme_secondary_pressed() {
         val interactionSource = MutableInteractionSource()
 
         var scope: CoroutineScope? = null
@@ -151,20 +130,20 @@
         composeTestRule.setContent {
             scope = rememberCoroutineScope()
             MaterialTheme(lightColorScheme()) {
-                DefaultLeadingIconTabs(interactionSource)
+                DefaultSecondaryTabs(interactionSource)
             }
         }
 
         assertTabsMatch(
             scope = scope!!,
             interactionSource = interactionSource,
-            interaction = null,
-            goldenIdentifier = "leadingIconTabs_lightTheme"
+            interaction = PressInteraction.Press(Offset(10f, 10f)),
+            goldenIdentifier = "tabs_lightTheme_secondary_pressed"
         )
     }
 
     @Test
-    fun leadingIconTabs_darkTheme() {
+    fun darkTheme_primary() {
         val interactionSource = MutableInteractionSource()
 
         var scope: CoroutineScope? = null
@@ -172,7 +151,7 @@
         composeTestRule.setContent {
             scope = rememberCoroutineScope()
             MaterialTheme(darkColorScheme()) {
-                DefaultLeadingIconTabs(interactionSource)
+                DefaultPrimaryTabs(interactionSource)
             }
         }
 
@@ -180,7 +159,342 @@
             scope = scope!!,
             interactionSource = interactionSource,
             interaction = null,
-            goldenIdentifier = "leadingIconTabs_darkTheme"
+            goldenIdentifier = "tabs_darkTheme_primary"
+        )
+    }
+
+    @Test
+    fun darkTheme_secondary() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(darkColorScheme()) {
+                DefaultSecondaryTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "tabs_darkTheme_secondary"
+        )
+    }
+
+    @Test
+    fun darkTheme_primary_pressed() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(darkColorScheme()) {
+                DefaultPrimaryTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = PressInteraction.Press(Offset(10f, 10f)),
+            goldenIdentifier = "tabs_darkTheme_primary_pressed"
+        )
+    }
+
+    @Test
+    fun darkTheme_secondary_pressed() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(darkColorScheme()) {
+                DefaultSecondaryTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = PressInteraction.Press(Offset(10f, 10f)),
+            goldenIdentifier = "tabs_darkTheme_secondary_pressed"
+        )
+    }
+
+    @Test
+    fun customTabs_lightTheme_primary() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(lightColorScheme()) {
+                CustomPrimaryTabs(
+                    interactionSource,
+                    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                    selectedContentColor = MaterialTheme.colorScheme.onTertiary,
+                    unselectedContentColor = Color.Black
+                )
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "customTabs_lightTheme_primary"
+        )
+    }
+
+    @Test
+    fun customTabs_lightTheme_secondary() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(lightColorScheme()) {
+                CustomSecondaryTabs(
+                    interactionSource,
+                    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                    selectedContentColor = MaterialTheme.colorScheme.onTertiary,
+                    unselectedContentColor = Color.Black
+                )
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "customTabs_lightTheme_secondary"
+        )
+    }
+
+    @Test
+    fun customTabs_darkTheme_primary() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(darkColorScheme()) {
+                CustomPrimaryTabs(
+                    interactionSource,
+                    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                    selectedContentColor = MaterialTheme.colorScheme.onTertiary,
+                    unselectedContentColor = Color.Black
+                )
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "customTabs_darkTheme_primary"
+        )
+    }
+
+    @Test
+    fun customTabs_darkTheme_secondary() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(darkColorScheme()) {
+                CustomSecondaryTabs(
+                    interactionSource,
+                    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                    selectedContentColor = MaterialTheme.colorScheme.onTertiary,
+                    unselectedContentColor = Color.Black
+                )
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "customTabs_darkTheme_secondary"
+        )
+    }
+
+    @Test
+    fun leadingIconTabs_lightTheme_primary() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(lightColorScheme()) {
+                DefaultPrimaryLeadingIconTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "leadingIconTabs_lightTheme_primary"
+        )
+    }
+
+    @Test
+    fun leadingIconTabs_lightTheme_secondary() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(lightColorScheme()) {
+                DefaultSecondaryLeadingIconTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "leadingIconTabs_lightTheme_secondary"
+        )
+    }
+
+    @Test
+    fun leadingIconTabs_darkTheme_primary() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(darkColorScheme()) {
+                DefaultPrimaryLeadingIconTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "leadingIconTabs_darkTheme_primary"
+        )
+    }
+
+    @Test
+    fun leadingIconTabs_darkTheme_secondary() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(darkColorScheme()) {
+                DefaultSecondaryLeadingIconTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "leadingIconTabs_darkTheme_secondary"
+        )
+    }
+
+    @Test
+    fun lightTheme_primary_scrollable() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(lightColorScheme()) {
+                DefaultPrimaryScrollableTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "tabs_lightTheme_primary_scrollable"
+        )
+    }
+
+    @Test
+    fun lightTheme_secondary_scrollable() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(lightColorScheme()) {
+                DefaultSecondaryScrollableTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "tabs_lightTheme_secondary_scrollable"
+        )
+    }
+
+    @Test
+    fun darkTheme_primary_scrollable() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(darkColorScheme()) {
+                DefaultPrimaryScrollableTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "tabs_darkTheme_primary_scrollable"
+        )
+    }
+
+    @Test
+    fun darkTheme_secondary_scrollable() {
+        val interactionSource = MutableInteractionSource()
+
+        var scope: CoroutineScope? = null
+
+        composeTestRule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme(darkColorScheme()) {
+                DefaultSecondaryScrollableTabs(interactionSource)
+            }
+        }
+
+        assertTabsMatch(
+            scope = scope!!,
+            interactionSource = interactionSource,
+            interaction = null,
+            goldenIdentifier = "tabs_darkTheme_secondary_scrollable"
         )
     }
 
@@ -213,23 +527,66 @@
         }
 
         // Capture and compare screenshots
-        composeTestRule.onNodeWithTag(Tag)
+        composeTestRule.onNodeWithTag(TAG)
             .captureToImage()
             .assertAgainstGolden(screenshotRule, goldenIdentifier)
     }
 }
 
 /**
- * Default colored [TabRow] with three [Tab]s. The first [Tab] is selected, and the rest are not.
+ * Default primary colored [TabRow] with three [Tab]s. The first [Tab] is selected, and the rest are not.
  *
  * @param interactionSource the [MutableInteractionSource] for the first [Tab], to control its
  * visual state.
  */
 @Composable
-private fun DefaultTabs(
+private fun DefaultPrimaryTabs(
     interactionSource: MutableInteractionSource
 ) {
-    Box(Modifier.semantics(mergeDescendants = true) {}.testTag(Tag)) {
+    Box(
+        Modifier
+            .semantics(mergeDescendants = true) {}
+            .testTag(TAG)) {
+        TabRow(selectedTabIndex = 0, indicator = @Composable { tabPositions ->
+            TabRowDefaults.PrimaryIndicator(
+                modifier = Modifier.tabIndicatorOffset(tabPositions[0]),
+                width = tabPositions[0].contentWidth
+            )
+        }) {
+            Tab(
+                selected = true,
+                onClick = {},
+                text = { Text("TAB") },
+                interactionSource = interactionSource
+            )
+            Tab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") }
+            )
+            Tab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") }
+            )
+        }
+    }
+}
+
+/**
+ * Default secondary colored [TabRow] with three [Tab]s. The first [Tab] is selected, and the rest are not.
+ *
+ * @param interactionSource the [MutableInteractionSource] for the first [Tab], to control its
+ * visual state.
+ */
+@Composable
+private fun DefaultSecondaryTabs(
+    interactionSource: MutableInteractionSource
+) {
+    Box(
+        Modifier
+            .semantics(mergeDescendants = true) {}
+            .testTag(TAG)) {
         TabRow(selectedTabIndex = 0) {
             Tab(
                 selected = true,
@@ -252,7 +609,7 @@
 }
 
 /**
- * Custom colored [TabRow] with three [Tab]s. The first [Tab] is selected, and the rest are not.
+ * Custom primary colored [TabRow] with three [Tab]s. The first [Tab] is selected, and the rest are not.
  *
  * @param interactionSource the [MutableInteractionSource] for the first [Tab], to control its
  * visual state.
@@ -261,17 +618,75 @@
  * @param unselectedContentColor the content color for an unselected [Tab] (second and third tabs)
  */
 @Composable
-private fun CustomTabs(
+private fun CustomPrimaryTabs(
     interactionSource: MutableInteractionSource,
     containerColor: Color,
     selectedContentColor: Color,
     unselectedContentColor: Color
 ) {
-    Box(Modifier.semantics(mergeDescendants = true) {}.testTag(Tag)) {
+    Box(
+        Modifier
+            .semantics(mergeDescendants = true) {}
+            .testTag(TAG)) {
         TabRow(selectedTabIndex = 0,
             containerColor = containerColor,
             indicator = @Composable { tabPositions ->
-                TabRowDefaults.Indicator(
+                TabRowDefaults.PrimaryIndicator(
+                    modifier = Modifier.tabIndicatorOffset(tabPositions[0]),
+                    width = tabPositions[0].contentWidth,
+                    color = selectedContentColor
+                )
+            }) {
+            Tab(
+                selected = true,
+                onClick = {},
+                text = { Text("TAB") },
+                selectedContentColor = selectedContentColor,
+                unselectedContentColor = unselectedContentColor,
+                interactionSource = interactionSource
+            )
+            Tab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") },
+                selectedContentColor = selectedContentColor,
+                unselectedContentColor = unselectedContentColor
+            )
+            Tab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") },
+                selectedContentColor = selectedContentColor,
+                unselectedContentColor = unselectedContentColor
+            )
+        }
+    }
+}
+
+/**
+ * Custom secondary colored [TabRow] with three [Tab]s. The first [Tab] is selected, and the rest are not.
+ *
+ * @param interactionSource the [MutableInteractionSource] for the first [Tab], to control its
+ * visual state.
+ * @param containerColor the containerColor of the [TabRow]
+ * @param selectedContentColor the content color for a selected [Tab] (first tab)
+ * @param unselectedContentColor the content color for an unselected [Tab] (second and third tabs)
+ */
+@Composable
+private fun CustomSecondaryTabs(
+    interactionSource: MutableInteractionSource,
+    containerColor: Color,
+    selectedContentColor: Color,
+    unselectedContentColor: Color
+) {
+    Box(
+        Modifier
+            .semantics(mergeDescendants = true) {}
+            .testTag(TAG)) {
+        TabRow(selectedTabIndex = 0,
+            containerColor = containerColor,
+            indicator = @Composable { tabPositions ->
+                TabRowDefaults.SecondaryIndicator(
                     modifier = Modifier.tabIndicatorOffset(tabPositions[0]),
                     color = selectedContentColor
                 )
@@ -303,17 +718,64 @@
 }
 
 /**
- * Default colored [TabRow] with three [LeadingIconTab]s. The first [LeadingIconTab] is selected,
+ * Default primary colored [TabRow] with three [LeadingIconTab]s. The first [LeadingIconTab] is selected,
  * and the rest are not.
  *
  * @param interactionSource the [MutableInteractionSource] for the first [LeadingIconTab], to control its
  * visual state.
  */
 @Composable
-private fun DefaultLeadingIconTabs(
+private fun DefaultPrimaryLeadingIconTabs(
     interactionSource: MutableInteractionSource
 ) {
-    Box(Modifier.semantics(mergeDescendants = true) {}.testTag(Tag)) {
+    Box(
+        Modifier
+            .semantics(mergeDescendants = true) {}
+            .testTag(TAG)) {
+        TabRow(selectedTabIndex = 0, indicator = @Composable { tabPositions ->
+            TabRowDefaults.PrimaryIndicator(
+                modifier = Modifier.tabIndicatorOffset(tabPositions[0]),
+                width = tabPositions[0].contentWidth
+            )
+        }) {
+            LeadingIconTab(
+                selected = true,
+                onClick = {},
+                text = { Text("TAB") },
+                icon = { Icon(Icons.Filled.Favorite, contentDescription = "Favorite") },
+                interactionSource = interactionSource
+            )
+            LeadingIconTab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") },
+                icon = { Icon(Icons.Filled.Favorite, contentDescription = "Favorite") }
+            )
+            LeadingIconTab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") },
+                icon = { Icon(Icons.Filled.Favorite, contentDescription = "Favorite") }
+            )
+        }
+    }
+}
+
+/**
+ * Default secondary colored [TabRow] with three [LeadingIconTab]s. The first [LeadingIconTab] is selected,
+ * and the rest are not.
+ *
+ * @param interactionSource the [MutableInteractionSource] for the first [LeadingIconTab], to control its
+ * visual state.
+ */
+@Composable
+private fun DefaultSecondaryLeadingIconTabs(
+    interactionSource: MutableInteractionSource
+) {
+    Box(
+        Modifier
+            .semantics(mergeDescendants = true) {}
+            .testTag(TAG)) {
         TabRow(selectedTabIndex = 0) {
             LeadingIconTab(
                 selected = true,
@@ -338,4 +800,79 @@
     }
 }
 
-private const val Tag = "Tab"
+/**
+ * Default primary colored [ScrollableTabRow] with three [Tab]s. The first [Tab] is selected, and the rest are not.
+ *
+ * @param interactionSource the [MutableInteractionSource] for the first [Tab], to control its
+ * visual state.
+ */
+@Composable
+private fun DefaultPrimaryScrollableTabs(
+    interactionSource: MutableInteractionSource
+) {
+    Box(
+        Modifier
+            .semantics(mergeDescendants = true) {}
+            .testTag(TAG)) {
+        ScrollableTabRow(selectedTabIndex = 0, indicator = @Composable { tabPositions ->
+            TabRowDefaults.PrimaryIndicator(
+                modifier = Modifier.tabIndicatorOffset(tabPositions[0]),
+                width = tabPositions[0].contentWidth
+            )
+        }) {
+            Tab(
+                selected = true,
+                onClick = {},
+                text = { Text("TAB") },
+                interactionSource = interactionSource
+            )
+            Tab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") }
+            )
+            Tab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") }
+            )
+        }
+    }
+}
+
+/**
+ * Default secondary colored [ScrollableTabRow] with three [Tab]s. The first [Tab] is selected, and the rest are not.
+ *
+ * @param interactionSource the [MutableInteractionSource] for the first [Tab], to control its
+ * visual state.
+ */
+@Composable
+private fun DefaultSecondaryScrollableTabs(
+    interactionSource: MutableInteractionSource
+) {
+    Box(
+        Modifier
+            .semantics(mergeDescendants = true) {}
+            .testTag(TAG)) {
+        ScrollableTabRow(selectedTabIndex = 0) {
+            Tab(
+                selected = true,
+                onClick = {},
+                text = { Text("TAB") },
+                interactionSource = interactionSource
+            )
+            Tab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") }
+            )
+            Tab(
+                selected = false,
+                onClick = {},
+                text = { Text("TAB") }
+            )
+        }
+    }
+}
+
+private const val TAG = "Tab"
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TabTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TabTest.kt
index 4ff880a..7b9ed6e 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TabTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TabTest.kt
@@ -27,7 +27,7 @@
 import androidx.compose.material3.samples.LeadingIconTabs
 import androidx.compose.material3.samples.ScrollingTextTabs
 import androidx.compose.material3.samples.TextTabs
-import androidx.compose.material3.tokens.PrimaryNavigationTabTokens
+import androidx.compose.material3.tokens.DividerTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
@@ -326,9 +326,9 @@
         rule.onNodeWithTag("divider", true)
             .assertPositionInRootIsEqualTo(
                 expectedLeft = 0.dp,
-                expectedTop = tabRowBounds.height - PrimaryNavigationTabTokens.DividerHeight
+                expectedTop = tabRowBounds.height - DividerTokens.Thickness
             )
-            .assertHeightIsEqualTo(PrimaryNavigationTabTokens.DividerHeight)
+            .assertHeightIsEqualTo(DividerTokens.Thickness)
     }
 
     @Test
@@ -435,7 +435,7 @@
     }
 
     @Test
-    fun LeadingIconTab_textAndIconPosition() {
+    fun leadingIconTab_textAndIconPosition() {
         rule.setMaterialContent(lightColorScheme()) {
             Box {
                 TabRow(
@@ -564,9 +564,9 @@
         rule.onNodeWithTag("divider", true)
             .assertPositionInRootIsEqualTo(
                 expectedLeft = 0.dp,
-                expectedTop = tabRowBounds.height - PrimaryNavigationTabTokens.DividerHeight,
+                expectedTop = tabRowBounds.height - DividerTokens.Thickness,
             )
-            .assertHeightIsEqualTo(PrimaryNavigationTabTokens.DividerHeight)
+            .assertHeightIsEqualTo(DividerTokens.Thickness)
     }
 
     @Test
@@ -593,8 +593,10 @@
                 TextTabs()
             }
 
+        val nodes = rule.onAllNodes(isSelectable())
+
         // Only the first tab should be selected
-        rule.onAllNodes(isSelectable())
+        nodes
             .assertCountEquals(3)
             .apply {
                 get(0).assertIsSelected()
@@ -603,10 +605,10 @@
             }
 
         // Click the last tab
-        rule.onAllNodes(isSelectable())[2].performClick()
+        nodes[2].performClick()
 
         // Now only the last tab should be selected
-        rule.onAllNodes(isSelectable())
+        nodes
             .assertCountEquals(3)
             .apply {
                 get(0).assertIsNotSelected()
@@ -747,7 +749,7 @@
             val titles = listOf("TAB 1", "TAB 2", "TAB 3 WITH LOTS OF TEXT")
 
             val indicator = @Composable { tabPositions: List<TabPosition> ->
-                TabRowDefaults.Indicator(
+                TabRowDefaults.SecondaryIndicator(
                     Modifier
                         .tabIndicatorOffset(tabPositions[state])
                         .testTag("indicator")
@@ -791,7 +793,7 @@
 
     @Test
     fun testInspectorValue() {
-        val pos = TabPosition(10.0.dp, 200.0.dp)
+        val pos = TabPosition(10.0.dp, 200.0.dp, 0.dp)
         rule.setContent {
             val modifier = Modifier.tabIndicatorOffset(pos) as InspectableValue
             assertThat(modifier.nameFallback).isEqualTo("tabIndicatorOffset")
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
index d726302..31ab35e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
@@ -315,7 +315,11 @@
                 ) { text() }
             }
             if (icon != null) {
-                Box(Modifier.layoutId("icon")) { icon() }
+                Box(
+                    Modifier
+                        .layoutId("icon")
+                        .padding(horizontal = HorizontalTextPadding)
+                ) { icon() }
             }
         }
     ) { measurables, constraints ->
@@ -430,7 +434,7 @@
 private const val TabFadeOutAnimationDuration = 100
 
 // The horizontal padding on the left and right of text
-private val HorizontalTextPadding = 16.dp
+internal val HorizontalTextPadding = 16.dp
 
 // Distance from the top of the indicator to the text baseline when there is one line of text and an
 // icon
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
index 6480116..82d1d85 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
@@ -24,9 +24,11 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.horizontalScroll
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.rememberScrollState
@@ -43,6 +45,8 @@
 import androidx.compose.ui.composed
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.SubcomposeLayout
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.Constraints
@@ -52,8 +56,9 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
-// TODO: Provide M3 tab row asset and docs when available.
 /**
+ * <a href="https://m3.material.io/components/tabs/overview" class="external" target="_blank">Material Design tabs</a>
+ *
  * Material Design fixed tabs.
  *
  * Fixed tabs display all tabs in a set simultaneously. They are best for switching between related
@@ -113,7 +118,7 @@
  * matching content color for [containerColor], or to the current [LocalContentColor] if
  * [containerColor] is not a color from the theme.
  * @param indicator the indicator that represents which tab is currently selected. By default this
- * will be a [TabRowDefaults.Indicator], using a [TabRowDefaults.tabIndicatorOffset] modifier to
+ * will be a [TabRowDefaults.SecondaryIndicator], using a [TabRowDefaults.tabIndicatorOffset] modifier to
  * animate its position. Note that this indicator will be forced to fill up the entire tab row, so
  * you should use [TabRowDefaults.tabIndicatorOffset] or similar to animate the actual drawn
  * indicator inside this space, and provide an offset from the start.
@@ -130,7 +135,7 @@
     contentColor: Color = TabRowDefaults.contentColor,
     indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
         if (selectedTabIndex < tabPositions.size) {
-            TabRowDefaults.Indicator(
+            TabRowDefaults.SecondaryIndicator(
                 Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
             )
         }
@@ -140,6 +145,18 @@
     },
     tabs: @Composable () -> Unit
 ) {
+    TabRowImpl(modifier, containerColor, contentColor, indicator, divider, tabs)
+}
+
+@Composable
+private fun TabRowImpl(
+    modifier: Modifier,
+    containerColor: Color,
+    contentColor: Color,
+    indicator: @Composable (tabPositions: List<TabPosition>) -> Unit,
+    divider: @Composable () -> Unit,
+    tabs: @Composable () -> Unit
+) {
     Surface(
         modifier = modifier.selectableGroup(),
         color = containerColor,
@@ -169,7 +186,10 @@
             }
 
             val tabPositions = List(tabCount) { index ->
-                TabPosition(tabWidth.toDp() * index, tabWidth.toDp())
+                var contentWidth =
+                    minOf(tabMeasurables[index].maxIntrinsicWidth(tabRowHeight), tabWidth).toDp()
+                contentWidth -= HorizontalTextPadding * 2
+                TabPosition(tabWidth.toDp() * index, tabWidth.toDp(), contentWidth)
             }
 
             layout(tabRowWidth, tabRowHeight) {
@@ -192,8 +212,9 @@
     }
 }
 
-// TODO: Provide M3 tab row asset and docs when available.
 /**
+ * <a href="https://m3.material.io/components/tabs/overview" class="external" target="_blank">Material Design tabs</a>
+ *
  * Material Design scrollable tabs.
  *
  * When a set of tabs cannot fit on screen, use scrollable tabs. Scrollable tabs can use longer text
@@ -215,7 +236,7 @@
  * and the tabs inside the row. This padding helps inform the user that this tab row can be
  * scrolled, unlike a [TabRow].
  * @param indicator the indicator that represents which tab is currently selected. By default this
- * will be a [TabRowDefaults.Indicator], using a [TabRowDefaults.tabIndicatorOffset] modifier to
+ * will be a [TabRowDefaults.SecondaryIndicator], using a [TabRowDefaults.tabIndicatorOffset] modifier to
  * animate its position. Note that this indicator will be forced to fill up the entire tab row, so
  * you should use [TabRowDefaults.tabIndicatorOffset] or similar to animate the actual drawn
  * indicator inside this space, and provide an offset from the start.
@@ -232,7 +253,7 @@
     contentColor: Color = TabRowDefaults.contentColor,
     edgePadding: Dp = ScrollableTabRowPadding,
     indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
-        TabRowDefaults.Indicator(
+        TabRowDefaults.SecondaryIndicator(
             Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
         )
     },
@@ -276,8 +297,20 @@
                 minHeight = layoutHeight,
                 maxHeight = layoutHeight,
             )
-            val tabPlaceables = tabMeasurables
-                .map { it.measure(tabConstraints) }
+
+            val tabPlaceables = mutableListOf<Placeable>()
+            val tabContentWidths = mutableListOf<Dp>()
+            tabMeasurables.forEach {
+                val placeable = it.measure(tabConstraints)
+                var contentWidth =
+                    minOf(
+                        it.maxIntrinsicWidth(placeable.height),
+                        placeable.width
+                    ).toDp()
+                contentWidth -= HorizontalTextPadding * 2
+                tabPlaceables.add(placeable)
+                tabContentWidths.add(contentWidth)
+            }
 
             val layoutWidth = tabPlaceables.fold(initial = padding * 2) { curr, measurable ->
                 curr + measurable.width
@@ -288,10 +321,16 @@
                 // Place the tabs
                 val tabPositions = mutableListOf<TabPosition>()
                 var left = padding
-                tabPlaceables.forEach {
-                    it.placeRelative(left, 0)
-                    tabPositions.add(TabPosition(left = left.toDp(), width = it.width.toDp()))
-                    left += it.width
+                tabPlaceables.forEachIndexed { index, placeable ->
+                    placeable.placeRelative(left, 0)
+                    tabPositions.add(
+                        TabPosition(
+                            left = left.toDp(),
+                            width = placeable.width.toDp(),
+                            contentWidth = tabContentWidths[index]
+                        )
+                    )
+                    left += placeable.width
                 }
 
                 // The divider is measured with its own height, and width equal to the total width
@@ -333,9 +372,11 @@
  * @property left the left edge's x position from the start of the [TabRow]
  * @property right the right edge's x position from the start of the [TabRow]
  * @property width the width of this tab
+ * @property contentWidth the content width of this tab
  */
 @Immutable
-class TabPosition internal constructor(val left: Dp, val width: Dp) {
+class TabPosition internal constructor(val left: Dp, val width: Dp, val contentWidth: Dp) {
+
     val right: Dp get() = left + width
 
     override fun equals(other: Any?): Boolean {
@@ -344,6 +385,7 @@
 
         if (left != other.left) return false
         if (width != other.width) return false
+        if (contentWidth != other.contentWidth) return false
 
         return true
     }
@@ -351,11 +393,12 @@
     override fun hashCode(): Int {
         var result = left.hashCode()
         result = 31 * result + width.hashCode()
+        result = 31 * result + contentWidth.hashCode()
         return result
     }
 
     override fun toString(): String {
-        return "TabPosition(left=$left, right=$right, width=$width)"
+        return "TabPosition(left=$left, right=$right, width=$width, contentWidth=$contentWidth)"
     }
 }
 
@@ -364,12 +407,14 @@
  */
 object TabRowDefaults {
     /** Default container color of a tab row. */
-    val containerColor: Color @Composable get() =
-        PrimaryNavigationTabTokens.ContainerColor.toColor()
+    val containerColor: Color
+        @Composable get() =
+            PrimaryNavigationTabTokens.ContainerColor.toColor()
 
     /** Default content color of a tab row. */
-    val contentColor: Color @Composable get() =
-        PrimaryNavigationTabTokens.ActiveLabelTextColor.toColor()
+    val contentColor: Color
+        @Composable get() =
+            PrimaryNavigationTabTokens.ActiveLabelTextColor.toColor()
 
     /**
      * Default indicator, which will be positioned at the bottom of the [TabRow], on top of the
@@ -380,6 +425,12 @@
      * @param color color of the indicator
      */
     @Composable
+    @Deprecated(
+        message = "Use SecondaryIndicator instead.",
+        replaceWith = ReplaceWith(
+            "SecondaryIndicator(modifier, height, color)"
+        )
+    )
     fun Indicator(
         modifier: Modifier = Modifier,
         height: Dp = PrimaryNavigationTabTokens.ActiveIndicatorHeight,
@@ -395,6 +446,54 @@
     }
 
     /**
+     * Primary indicator, which will be positioned at the bottom of the [TabRow], on top of the
+     * divider.
+     *
+     * @param modifier modifier for the indicator's layout
+     * @param width width of the indicator
+     * @param height height of the indicator
+     * @param color color of the indicator
+     * @param shape shape of the indicator
+     */
+    @Composable
+    fun PrimaryIndicator(
+        modifier: Modifier = Modifier,
+        width: Dp = 0.dp,
+        height: Dp = PrimaryNavigationTabTokens.ActiveIndicatorHeight,
+        color: Color = PrimaryNavigationTabTokens.ActiveIndicatorColor.toColor(),
+        shape: Shape = PrimaryNavigationTabTokens.ActiveIndicatorShape
+    ) {
+        Spacer(
+            modifier
+                .requiredSize(width, height)
+                .background(color = color, shape = shape)
+        )
+    }
+
+    /**
+     * Secondary indicator, which will be positioned at the bottom of the [TabRow], on top of the
+     * divider.
+     *
+     * @param modifier modifier for the indicator's layout
+     * @param height height of the indicator
+     * @param color color of the indicator
+     */
+    @Composable
+    fun SecondaryIndicator(
+        modifier: Modifier = Modifier,
+        height: Dp = PrimaryNavigationTabTokens.ActiveIndicatorHeight,
+        color: Color =
+            MaterialTheme.colorScheme.fromToken(PrimaryNavigationTabTokens.ActiveIndicatorColor)
+    ) {
+        Box(
+            modifier
+                .fillMaxWidth()
+                .height(height)
+                .background(color = color)
+        )
+    }
+
+    /**
      * [Modifier] that takes up all the available width inside the [TabRow], and then animates
      * the offset of the indicator it is applied to, depending on the [currentTabPosition].
      *
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PrimaryNavigationTabTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PrimaryNavigationTabTokens.kt
index 3fd0e91..4622e75 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PrimaryNavigationTabTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PrimaryNavigationTabTokens.kt
@@ -13,7 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_103
+
+// VERSION: v0_162
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
@@ -29,8 +30,6 @@
     val ContainerElevation = ElevationTokens.Level0
     val ContainerHeight = 48.0.dp
     val ContainerShape = ShapeKeyTokens.CornerNone
-    val DividerColor = ColorSchemeKeyTokens.SurfaceVariant
-    val DividerHeight = 1.0.dp
     val ActiveFocusIconColor = ColorSchemeKeyTokens.Primary
     val ActiveHoverIconColor = ColorSchemeKeyTokens.Primary
     val ActiveIconColor = ColorSchemeKeyTokens.Primary
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SecondaryNavigationTabTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SecondaryNavigationTabTokens.kt
new file mode 100644
index 0000000..6d34a48
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SecondaryNavigationTabTokens.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// VERSION: v0_162
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object SecondaryNavigationTabTokens {
+    val ActiveLabelTextColor = ColorSchemeKeyTokens.OnSurface
+    val ContainerColor = ColorSchemeKeyTokens.Surface
+    val ContainerElevation = ElevationTokens.Level0
+    val ContainerHeight = 48.0.dp
+    val ContainerShape = ShapeKeyTokens.CornerNone
+    val DividerColor = ColorSchemeKeyTokens.SurfaceVariant
+    val DividerHeight = 1.0.dp
+    val FocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
+    val HoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
+    val InactiveLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val LabelTextFont = TypographyKeyTokens.TitleSmall
+    val PressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
+    val ActiveIconColor = ColorSchemeKeyTokens.OnSurface
+    val FocusIconColor = ColorSchemeKeyTokens.OnSurface
+    val HoverIconColor = ColorSchemeKeyTokens.OnSurface
+    val IconSize = 24.0.dp
+    val InactiveIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val PressedIconColor = ColorSchemeKeyTokens.OnSurface
+}
\ No newline at end of file