Merge "Enforce minimum space for text in M3 text fields." into androidx-main
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
index ae46835..fa4a4be 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
@@ -76,6 +76,7 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.MediumTest
@@ -86,7 +87,6 @@
 import com.nhaarman.mockitokotlin2.eq
 import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.verify
-import kotlin.math.max
 import kotlin.math.roundToInt
 import org.junit.Rule
 import org.junit.Test
@@ -227,39 +227,58 @@
 
     @Test
     fun testOutlinedTextField_labelPosition_initial_singleLine() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    singleLine = true,
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                singleLine = true,
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is centered, plus additional padding allowance on top
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                ((ExpectedMinimumTextFieldHeight - MinTextLineHeight) / 2 +
+                    OutlinedTextFieldTopPadding).toPx()
             )
-            // label is centered in 56.dp default container, plus additional 8.dp padding on top
-            val minimumHeight = ExpectedMinimumTextFieldHeight.roundToPx()
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ((minimumHeight - labelSize.value!!.height) / 2f).roundToInt() + 8.dp.roundToPx()
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_labelPosition_initial_withDefaultHeight() {
+        val labelPosition = Ref<Offset>()
+        rule.setMaterialContent(lightColorScheme()) {
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
+        }
+
+        rule.runOnIdleWithDensity {
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + default padding + label padding allowance
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
             )
         }
     }
@@ -268,22 +287,22 @@
     fun testOutlinedTextField_labelPosition_initial_withMultiLineLabel() {
         val textFieldWidth = 200.dp
         val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.requiredWidth(textFieldWidth),
-                    label = {
-                        Text(
-                            text = "long long long long long long long long long long long long",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                modifier = Modifier.requiredWidth(textFieldWidth),
+                label = {
+                    Text(
+                        text = "long long long long long long long long long long long long",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelSize.value = it.size
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
@@ -292,50 +311,20 @@
             assertThat(labelSize.value?.height).isGreaterThan(0)
             assertThat(labelSize.value?.width)
                 .isEqualTo(textFieldWidth.roundToPx() - 2 * ExpectedPadding.roundToPx())
-        }
-    }
 
-    @Test
-    fun testOutlinedTextField_labelPosition_initial_withDefaultHeight() {
-        val labelSize = Ref<IntSize>()
-        val labelPosition = Ref<Offset>()
-        rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
-        }
-
-        rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            // label is aligned to the top with padding, plus additional 8.dp padding on top
-            assertThat(labelPosition.value?.y).isEqualTo(
-                TextFieldPadding.roundToPx() + 8.dp.roundToPx()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + default padding + label padding allowance
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
             )
         }
     }
 
     @Test
     fun testOutlinedTextField_labelPosition_whenFocused() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
+        val labelSize = MinFocusedLabelLineHeight
 
         rule.setMaterialContent(lightColorScheme()) {
             OutlinedTextField(
@@ -343,11 +332,10 @@
                 value = "",
                 onValueChange = {},
                 label = {
-                    Text(
-                        text = "label",
-                        modifier = Modifier.onGloballyPositioned {
+                    Box(Modifier
+                        .size(MinFocusedLabelLineHeight)
+                        .onGloballyPositioned {
                             labelPosition.value = it.positionInRoot()
-                            labelSize.value = it.size
                         }
                     )
                 }
@@ -358,15 +346,10 @@
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // label position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                getLabelPosition(labelSize.roundToPx()).toFloat()
             )
-            assertThat(labelPosition.value?.y).isEqualTo(getLabelPosition(labelSize))
         }
     }
 
@@ -374,22 +357,22 @@
     fun testOutlinedTextField_labelPosition_whenFocused_withMultiLineLabel() {
         val textFieldWidth = 200.dp
         val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.testTag(TextFieldTag).requiredWidth(textFieldWidth),
-                    label = {
-                        Text(
-                            text = "long long long long long long long long long long long long",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                modifier = Modifier.testTag(TextFieldTag).requiredWidth(textFieldWidth),
+                label = {
+                    Text(
+                        text = "long long long long long long long long long long long long",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelSize.value = it.size
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         // click to focus
@@ -399,8 +382,15 @@
             // label size
             assertThat(labelSize.value).isNotNull()
             assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width)
-                .isEqualTo(textFieldWidth.roundToPx() - 2 * ExpectedPadding.roundToPx())
+            assertThat(labelSize.value?.width!!.toFloat()).isWithin(1f).of(
+                (textFieldWidth - ExpectedPadding * 2).toPx()
+            )
+
+            // label position
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                getLabelPosition(labelSize.value!!.height).toFloat()
+            )
         }
     }
 
@@ -455,18 +445,17 @@
 
     @Test
     fun testOutlinedTextField_labelPosition_whenInput() {
-        val labelSize = Ref<IntSize>()
+        val labelSize = MinFocusedLabelLineHeight
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
             OutlinedTextField(
                 value = "input",
                 onValueChange = {},
                 label = {
-                    Text(
-                        text = "label",
-                        modifier = Modifier.onGloballyPositioned {
+                    Box(Modifier
+                        .size(labelSize)
+                        .onGloballyPositioned {
                             labelPosition.value = it.positionInRoot()
-                            labelSize.value = it.size
                         }
                     )
                 }
@@ -474,15 +463,11 @@
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
             // label position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                getLabelPosition(labelSize.roundToPx()).toFloat()
             )
-            assertThat(labelPosition.value?.y).isEqualTo(getLabelPosition(labelSize))
         }
     }
 
@@ -513,7 +498,7 @@
 
     @Test
     fun testOutlinedTextField_placeholderPosition_withLabel() {
-        val placeholderSize = Ref<IntSize>()
+        val placeholderSize = MinTextLineHeight
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
             Box {
@@ -521,13 +506,12 @@
                     modifier = Modifier.testTag(TextFieldTag),
                     value = "",
                     onValueChange = {},
-                    label = { Text("label") },
+                    label = { Box(Modifier.size(MinFocusedLabelLineHeight)) },
                     placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(placeholderSize)
+                            .onGloballyPositioned {
                                 placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
                             }
                         )
                     }
@@ -538,57 +522,38 @@
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isGreaterThan(0)
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // position
-            assertThat(placeholderPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            // placeholder is aligned to the top with padding, plus additional 8.dp padding on top
-            assertThat(placeholderPosition.value?.y).isEqualTo(
-                TextFieldPadding.roundToPx() + 8.dp.roundToPx()
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(
+                (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
             )
         }
     }
 
     @Test
     fun testOutlinedTextField_placeholderPosition_whenNoLabel() {
-        val placeholderSize = Ref<IntSize>()
+        val placeholderSize = MinTextLineHeight
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    modifier = Modifier.testTag(TextFieldTag),
-                    value = "",
-                    onValueChange = {},
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
-                                placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                placeholder = {
+                    Box(Modifier
+                        .size(placeholderSize)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
         // click to focus
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isGreaterThan(0)
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // position
-            assertThat(placeholderPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            // placeholder is placed with fixed padding
-            assertThat(placeholderPosition.value?.y).isEqualTo(TextFieldPadding.roundToPx())
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
@@ -597,22 +562,20 @@
         val placeholderSize = Ref<IntSize>()
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Column {
-                OutlinedTextField(
-                    modifier = Modifier.testTag(TextFieldTag),
-                    value = "input",
-                    onValueChange = {},
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
-                                placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "input",
+                onValueChange = {},
+                placeholder = {
+                    Text(
+                        text = "placeholder",
+                        modifier = Modifier.onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                            placeholderSize.value = it.size
+                        }
+                    )
+                }
+            )
         }
 
         // click to focus
@@ -644,6 +607,61 @@
     }
 
     @Test
+    fun testOutlinedTextField_labelAndPlaceholderPosition_whenSmallerThanMinimumHeight() {
+        val labelSize = 10.dp
+        val labelPosition = Ref<Offset>()
+        val placeholderSize = 20.dp
+        val placeholderPosition = Ref<Offset>()
+        rule.setMaterialContent(lightColorScheme()) {
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(labelSize)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                placeholder = {
+                    Box(Modifier
+                        .size(placeholderSize)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
+        }
+
+        // click to focus
+        rule.onNodeWithTag(TextFieldTag).performClick()
+
+        rule.runOnIdleWithDensity {
+            // size
+            assertThat(labelSize).isLessThan(MinFocusedLabelLineHeight)
+            assertThat(placeholderSize).isLessThan(MinTextLineHeight)
+
+            // label position
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                getLabelPosition(labelSize.roundToPx()).toFloat()
+            )
+
+            // placeholder position
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // placeholder y position is top + default padding + label padding allowance, then
+            // centered within allocated space
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(
+                (ExpectedPadding + OutlinedTextFieldTopPadding +
+                    (MinTextLineHeight - placeholderSize) / 2).toPx()
+            )
+        }
+    }
+
+    @Test
     fun testOutlinedTextField_trailingAndLeading_sizeAndPosition_defaultIcon() {
         val textFieldWidth = 300.dp
         val leadingPosition = Ref<Offset>()
@@ -841,11 +859,10 @@
     @Test
     fun testOutlinedTextField_prefixAndSuffixPosition_withLabel() {
         val textFieldWidth = 300.dp
-        val textFieldHeight = 60.dp
-        val labelSize = Ref<IntSize>()
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -853,29 +870,21 @@
                 OutlinedTextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(textFieldWidth, textFieldHeight),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    },
+                    modifier = Modifier.width(textFieldWidth),
+                    label = { Box(Modifier.size(MinFocusedLabelLineHeight)) },
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     }
@@ -888,14 +897,16 @@
                 // prefix
                 assertThat(prefixPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
                 assertThat(prefixPosition.value?.y).isWithin(1f).of(
-                    (ExpectedPadding + 8.dp).toPx())
+                    (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
+                )
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (textFieldWidth - ExpectedPadding - suffixSize.value!!.width.toDp()).toPx()
+                    (textFieldWidth - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(
-                    (ExpectedPadding + 8.dp).toPx())
+                    (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
+                )
             }
         }
     }
@@ -903,10 +914,10 @@
     @Test
     fun testOutlinedTextField_prefixAndSuffixPosition_whenNoLabel() {
         val textFieldWidth = 300.dp
-        val textFieldHeight = 60.dp
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -914,21 +925,20 @@
                 OutlinedTextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(textFieldWidth, textFieldHeight),
+                    modifier = Modifier.width(textFieldWidth),
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     }
@@ -944,7 +954,7 @@
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (textFieldWidth - ExpectedPadding - suffixSize.value!!.width.toDp()).toPx()
+                    (textFieldWidth - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
             }
@@ -954,10 +964,10 @@
     @Test
     fun testOutlinedTextField_prefixAndSuffixPosition_withIcons() {
         val textFieldWidth = 300.dp
-        val textFieldHeight = 60.dp
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -965,21 +975,20 @@
                 OutlinedTextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(textFieldWidth, textFieldHeight),
+                    modifier = Modifier.width(textFieldWidth),
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     },
@@ -995,13 +1004,13 @@
 
                 // prefix
                 assertThat(prefixPosition.value?.x).isWithin(1f).of(
-                    (ExpectedPadding + IconPadding + iconSize).toPx())
+                    (ExpectedPadding + IconPadding + iconSize).toPx()
+                )
                 assertThat(prefixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (textFieldWidth - IconPadding - iconSize - ExpectedPadding -
-                        suffixSize.value!!.width.toDp()).toPx()
+                    (textFieldWidth - IconPadding - iconSize - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
             }
@@ -1012,28 +1021,26 @@
     fun testOutlinedTextField_labelPositionX_initial_withTrailingAndLeading() {
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                            }
-                        )
-                    },
-                    trailingIcon = { Icon(Icons.Default.Favorite, null) },
-                    leadingIcon = { Icon(Icons.Default.Favorite, null) }
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Text(
+                        text = "label",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                trailingIcon = { Icon(Icons.Default.Favorite, null) },
+                leadingIcon = { Icon(Icons.Default.Favorite, null) }
+            )
         }
 
         rule.runOnIdleWithDensity {
             val iconSize = 24.dp // default icon size
-            assertThat(labelPosition.value?.x).isEqualTo(
-                (ExpectedPadding + IconPadding + iconSize).roundToPx().toFloat()
+            assertThat(labelPosition.value?.x).isWithin(1f).of(
+                (ExpectedPadding + IconPadding + iconSize).toPx()
             )
         }
     }
@@ -1042,28 +1049,24 @@
     fun testOutlinedTextField_labelPositionX_initial_withNullTrailingAndLeading() {
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                            }
-                        )
-                    },
-                    trailingIcon = null,
-                    leadingIcon = null
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Text(
+                        text = "label",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                trailingIcon = null,
+                leadingIcon = null
+            )
         }
 
         rule.runOnIdleWithDensity {
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
@@ -1106,21 +1109,16 @@
 
     @Test
     fun testOutlinedTextField_supportingText_position() {
-        val tfSize = Ref<IntSize>()
-        val supportingSize = Ref<IntSize>()
         val supportingPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
             OutlinedTextField(
                 value = "",
                 onValueChange = {},
-                modifier = Modifier.onGloballyPositioned {
-                    tfSize.value = it.size
-                },
+                textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                 supportingText = {
-                    Text(
-                        text = "Supporting",
-                        modifier = Modifier.onGloballyPositioned {
-                            supportingSize.value = it.size
+                    Box(Modifier
+                        .size(MinSupportingTextLineHeight)
+                        .onGloballyPositioned {
                             supportingPosition.value = it.positionInRoot()
                         }
                     )
@@ -1129,11 +1127,9 @@
         }
 
         rule.runOnIdleWithDensity {
-            assertThat(supportingPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(supportingPosition.value?.y).isEqualTo(
-                tfSize.value!!.height - supportingSize.value!!.height
+            assertThat(supportingPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(supportingPosition.value?.y).isWithin(1f).of(
+                (ExpectedMinimumTextFieldHeight + SupportingTopPadding).toPx()
             )
         }
     }
@@ -1277,7 +1273,7 @@
     }
 
     @Test
-    fun testErrorSemantics_defaultMessage() {
+    fun testOutlinedTextField_errorSemantics_defaultMessage() {
         lateinit var errorMessage: String
         rule.setMaterialContent(lightColorScheme()) {
             OutlinedTextField(
@@ -1296,7 +1292,7 @@
     }
 
     @Test
-    fun testErrorSemantics_messageOverridable() {
+    fun testOutlinedTextField_errorSemantics_messageOverridable() {
         val errorMessage = "Special symbols not allowed"
         rule.setMaterialContent(lightColorScheme()) {
             val isError = remember { mutableStateOf(true) }
@@ -1602,7 +1598,7 @@
                     OutlinedTextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") }
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                     )
                     Divider(Modifier.fillMaxHeight())
                 }
@@ -1626,7 +1622,7 @@
                     OutlinedTextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") },
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                         leadingIcon = { Icon(Icons.Default.Favorite, null) }
                     )
                     Divider(Modifier.fillMaxHeight())
@@ -1651,7 +1647,7 @@
                     OutlinedTextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") },
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                         trailingIcon = { Icon(Icons.Default.Favorite, null) }
                     )
                     Divider(Modifier.fillMaxHeight())
@@ -1697,11 +1693,11 @@
         }
     }
 
-    private fun getLabelPosition(labelSize: Ref<IntSize>): Int {
-        val labelHalfHeight = labelSize.value!!.height / 2
+    private fun getLabelPosition(labelHeight: Int): Int {
+        val labelHalfHeight = labelHeight / 2
         val paddingTop = with(rule.density) { OutlinedTextFieldTopPadding.toPx() }
-        // vertical position is the default padding - half height
-        // in case negative position, fix to 0
-        return max(paddingTop - labelHalfHeight, 0f).roundToInt()
+        // Vertical position is the default padding - half height.
+        // This can be negative, meaning default padding is not enough for the focused label.
+        return (paddingTop - labelHalfHeight).roundToInt()
     }
 }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
index 447e330..4b994e6 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
@@ -31,7 +31,6 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
@@ -126,7 +125,7 @@
     val rule = createComposeRule()
 
     @Test
-    fun testTextField_minimumHeight() {
+    fun testTextField_setSmallHeight() {
         rule.setMaterialContentForSizeAssertions {
             TextField(
                 value = "input",
@@ -150,6 +149,18 @@
     }
 
     @Test
+    fun testTextField_defaultHeight() {
+        rule.setMaterialContentForSizeAssertions {
+            TextField(
+                value = "",
+                onValueChange = {},
+                textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
+            )
+        }
+            .assertHeightIsEqualTo(ExpectedDefaultTextFieldHeight)
+    }
+
+    @Test
     fun testTextField_defaultWidth() {
         rule.setMaterialContentForSizeAssertions {
             TextField(
@@ -322,293 +333,207 @@
 
     @Test
     fun testTextField_labelPosition_initial_singleLine() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    singleLine = true,
-                    label = {
-                        Text(
-                            text = "label",
-                            fontSize = 10.sp,
-                            modifier = Modifier
-                                .onGloballyPositioned {
-                                    labelPosition.value = it.positionInRoot()
-                                    labelSize.value = it.size
-                                }
-                        )
-                    },
-                    modifier = Modifier.height(56.dp)
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                singleLine = true,
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // centered position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ((ExpectedDefaultTextFieldHeight.roundToPx() - labelSize.value!!.height) / 2f)
-                    .roundToInt().toFloat()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is centered
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                (ExpectedDefaultTextFieldHeight - MinTextLineHeight).toPx() / 2f
             )
         }
     }
 
     @Test
     fun testTextField_labelPosition_initial_withDefaultHeight() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            fontSize = 10.sp,
-                            modifier = Modifier
-                                .onGloballyPositioned {
-                                    labelPosition.value = it.positionInRoot()
-                                    labelSize.value = it.size
-                                }
-                        )
-                    },
-                    modifier = Modifier.height(56.dp)
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // centered position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ExpectedPadding.roundToPx()
-            )
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + padding
+            assertThat(labelPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
     @Test
     fun testTextField_labelPosition_initial_withCustomHeight() {
         val height = 80.dp
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.height(height),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                modifier = Modifier.height(height),
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ExpectedPadding.roundToPx()
-            )
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + padding
+            assertThat(labelPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
     @Test
     fun testTextField_labelPosition_whenFocused() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    modifier = Modifier.testTag(TextFieldTag),
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(MinFocusedLabelLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         // click to focus
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                TextFieldWithLabelVerticalPadding.roundToPx().toFloat()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + (different) padding
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                TextFieldWithLabelVerticalPadding.toPx()
             )
         }
     }
 
     @Test
     fun testTextField_labelPosition_whenInput() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "input",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                value = "input",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(MinFocusedLabelLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                TextFieldWithLabelVerticalPadding.roundToPx().toFloat()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + (different) padding
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                TextFieldWithLabelVerticalPadding.toPx()
             )
         }
     }
 
     @Test
     fun testTextField_placeholderPosition_withLabel() {
-        val labelSize = Ref<IntSize>()
-        val placeholderSize = Ref<IntSize>()
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    modifier = Modifier
-                        .height(60.dp)
-                        .testTag(TextFieldTag),
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    },
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
-                                placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                label = { Box(Modifier.size(MinFocusedLabelLineHeight)) },
+                placeholder = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
+
         // click to focus
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isGreaterThan(0)
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // placeholder's position
+            // x position is start + padding
             assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
-            assertThat(placeholderPosition.value?.y).isWithin(1f)
-                .of(
-                    TextFieldWithLabelVerticalPadding.toPx() +
-                        labelSize.value!!.height.toFloat()
-                )
+            // y position is top + padding + label height
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(
+                (TextFieldWithLabelVerticalPadding + MinFocusedLabelLineHeight).toPx()
+            )
         }
     }
 
     @Test
     fun testTextField_placeholderPosition_whenNoLabel() {
-        val placeholderSize = Ref<IntSize>()
         val placeholderPosition = Ref<Offset>()
-        val height = 60.dp
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    modifier = Modifier.height(height).testTag(TextFieldTag),
-                    value = "",
-                    onValueChange = {},
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.requiredHeight(20.dp)
-                                .onGloballyPositioned {
-                                    placeholderPosition.value = it.positionInRoot()
-                                    placeholderSize.value = it.size
-                                }
-                        )
-                    }
-                )
-            }
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                placeholder = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
+
         // click to focus
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isEqualTo(20.dp.roundToPx())
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // centered position
-            assertThat(placeholderPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(placeholderPosition.value?.y).isEqualTo(
-                TextFieldPadding.roundToPx()
-            )
+            // x position is start + padding
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + padding
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
@@ -617,23 +542,20 @@
         val placeholderSize = Ref<IntSize>()
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Column {
-                TextField(
-                    modifier = Modifier.testTag(TextFieldTag),
-                    value = "input",
-                    onValueChange = {},
-
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
-                                placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "input",
+                onValueChange = {},
+                placeholder = {
+                    Text(
+                        text = "placeholder",
+                        modifier = Modifier.onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                            placeholderSize.value = it.size
+                        }
+                    )
+                }
+            )
         }
 
         // click to focus
@@ -665,6 +587,63 @@
     }
 
     @Test
+    fun testTextField_labelAndPlaceholderPosition_whenSmallerThanMinimumHeight() {
+        val labelSize = 10.dp
+        val labelPosition = Ref<Offset>()
+        val placeholderSize = 20.dp
+        val placeholderPosition = Ref<Offset>()
+        rule.setMaterialContent(lightColorScheme()) {
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(labelSize)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                placeholder = {
+                    Box(Modifier
+                        .size(placeholderSize)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
+        }
+
+        // click to focus
+        rule.onNodeWithTag(TextFieldTag).performClick()
+
+        rule.runOnIdleWithDensity {
+            // size
+            assertThat(labelSize).isLessThan(MinFocusedLabelLineHeight)
+            assertThat(placeholderSize).isLessThan(MinTextLineHeight)
+
+            // label x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // label y position is top + padding, then centered within allocated space
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                (TextFieldWithLabelVerticalPadding + (MinFocusedLabelLineHeight - labelSize) / 2)
+                    .toPx()
+            )
+
+            // placeholder x position is start + padding
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // placeholder y position is top + padding + label height, then centered within
+            // allocated space
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(
+                (TextFieldWithLabelVerticalPadding + MinFocusedLabelLineHeight +
+                    (MinTextLineHeight - placeholderSize) / 2).toPx()
+            )
+        }
+    }
+
+    @Test
     fun testTextField_trailingAndLeading_sizeAndPosition_defaultIcon() {
         val textFieldHeight = 60.dp
         val leadingPosition = Ref<Offset>()
@@ -868,11 +847,10 @@
 
     @Test
     fun testTextField_prefixAndSuffixPosition_withLabel() {
-        val textFieldHeight = 60.dp
-        val labelSize = Ref<IntSize>()
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -880,29 +858,21 @@
                 TextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(TextFieldWidth, textFieldHeight),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    },
+                    modifier = Modifier.width(TextFieldWidth),
+                    label = { Box(Modifier.size(MinFocusedLabelLineHeight)) },
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     }
@@ -914,33 +884,27 @@
             with(density) {
                 // prefix
                 assertThat(prefixPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
-                assertThat(prefixPosition.value?.y)
-                    .isWithin(1f)
-                    .of(
-                        TextFieldWithLabelVerticalPadding.toPx() +
-                            labelSize.value!!.height.toFloat()
-                    )
+                assertThat(prefixPosition.value?.y).isWithin(1f).of(
+                    (TextFieldWithLabelVerticalPadding + MinFocusedLabelLineHeight).toPx()
+                )
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (TextFieldWidth - ExpectedPadding - suffixSize.value!!.width.toDp()).toPx()
+                    (TextFieldWidth - ExpectedPadding - suffixSize).toPx()
                 )
-                assertThat(suffixPosition.value?.y)
-                    .isWithin(1f)
-                    .of(
-                        TextFieldWithLabelVerticalPadding.toPx() +
-                            labelSize.value!!.height.toFloat()
-                    )
+                assertThat(suffixPosition.value?.y).isWithin(1f).of(
+                    (TextFieldWithLabelVerticalPadding + MinFocusedLabelLineHeight).toPx()
+                )
             }
         }
     }
 
     @Test
     fun testTextField_prefixAndSuffixPosition_whenNoLabel() {
-        val textFieldHeight = 60.dp
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -948,21 +912,20 @@
                 TextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(TextFieldWidth, textFieldHeight),
+                    modifier = Modifier.width(TextFieldWidth),
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     }
@@ -978,7 +941,7 @@
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (TextFieldWidth - ExpectedPadding - suffixSize.value!!.width.toDp()).toPx()
+                    (TextFieldWidth - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
             }
@@ -987,10 +950,10 @@
 
     @Test
     fun testTextField_prefixAndSuffixPosition_withIcons() {
-        val textFieldHeight = 60.dp
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -998,21 +961,20 @@
                 TextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(TextFieldWidth, textFieldHeight),
+                    modifier = Modifier.width(TextFieldWidth),
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     },
@@ -1028,13 +990,13 @@
 
                 // prefix
                 assertThat(prefixPosition.value?.x).isWithin(1f).of(
-                    (ExpectedPadding + IconPadding + iconSize).toPx())
+                    (ExpectedPadding + IconPadding + iconSize).toPx()
+                )
                 assertThat(prefixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (TextFieldWidth - IconPadding - iconSize - ExpectedPadding -
-                        suffixSize.value!!.width.toDp()).toPx()
+                    (TextFieldWidth - IconPadding - iconSize - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
             }
@@ -1043,64 +1005,54 @@
 
     @Test
     fun testTextField_labelPositionX_initial_withTrailingAndLeading() {
-        val height = 60.dp
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.height(height),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                            }
-                        )
-                    },
-                    trailingIcon = { Icon(Icons.Default.Favorite, null) },
-                    leadingIcon = { Icon(Icons.Default.Favorite, null) }
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Text(
+                        text = "label",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                trailingIcon = { Icon(Icons.Default.Favorite, null) },
+                leadingIcon = { Icon(Icons.Default.Favorite, null) }
+            )
         }
 
         rule.runOnIdleWithDensity {
             val iconSize = 24.dp // default icon size
-            assertThat(labelPosition.value?.x).isEqualTo(
-                (ExpectedPadding + IconPadding + iconSize).roundToPx().toFloat()
+            assertThat(labelPosition.value?.x).isWithin(1f).of(
+                (ExpectedPadding + IconPadding + iconSize).toPx()
             )
         }
     }
 
     @Test
     fun testTextField_labelPositionX_initial_withNullTrailingAndLeading() {
-        val height = 60.dp
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.height(height),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                            }
-                        )
-                    },
-                    trailingIcon = null,
-                    leadingIcon = null
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Text(
+                        text = "label",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                trailingIcon = null,
+                leadingIcon = null
+            )
         }
 
         rule.runOnIdleWithDensity {
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
@@ -1142,22 +1094,17 @@
     }
 
     @Test
-    fun testTextField_supportingText_position() {
-        val tfSize = Ref<IntSize>()
-        val supportingSize = Ref<IntSize>()
+    fun testTextField_supportingTextPosition_withDefaultHeight() {
         val supportingPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
             TextField(
                 value = "",
                 onValueChange = {},
-                modifier = Modifier.onGloballyPositioned {
-                    tfSize.value = it.size
-                },
+                textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                 supportingText = {
-                    Text(
-                        text = "Supporting",
-                        modifier = Modifier.onGloballyPositioned {
-                            supportingSize.value = it.size
+                    Box(Modifier
+                        .size(MinSupportingTextLineHeight)
+                        .onGloballyPositioned {
                             supportingPosition.value = it.positionInRoot()
                         }
                     )
@@ -1166,11 +1113,9 @@
         }
 
         rule.runOnIdleWithDensity {
-            assertThat(supportingPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(supportingPosition.value?.y).isEqualTo(
-                tfSize.value!!.height - supportingSize.value!!.height
+            assertThat(supportingPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(supportingPosition.value?.y).isWithin(1f).of(
+                (ExpectedDefaultTextFieldHeight + SupportingTopPadding).toPx()
             )
         }
     }
@@ -1374,7 +1319,7 @@
     @Test
     @LargeTest
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun testTransformedTextIsUsed_toDefineLabelPosition() {
+    fun testTextField_transformedTextIsUsed_toDefineLabelPosition() {
         // if non-transformed value were used to check if the text input is empty, the label
         // wouldn't be aligned to the top, as a result it would be obscured by text
         val prefixTransformation = VisualTransformation { text ->
@@ -1410,7 +1355,7 @@
     @Test
     @LargeTest
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun testTransformedTextIsUsed_toDefineIfPlaceholderNeeded() {
+    fun testTextField_transformedTextIsUsed_toDefineIfPlaceholderNeeded() {
         // if original value were used to check if the text input is empty, the placeholder would be
         // displayed on top of the text
         val prefixTransformation = VisualTransformation { text ->
@@ -1454,7 +1399,7 @@
     }
 
     @Test
-    fun testErrorSemantics_defaultMessage() {
+    fun testTextField_errorSemantics_defaultMessage() {
         lateinit var errorMessage: String
         rule.setMaterialContent(lightColorScheme()) {
             TextField(
@@ -1471,10 +1416,10 @@
     }
 
     @Test
-    fun testErrorSemantics_messageOverridable() {
+    fun testTextField_errorSemantics_messageOverridable() {
         val errorMessage = "Special symbols not allowed"
         rule.setMaterialContent(lightColorScheme()) {
-            var isError = remember { mutableStateOf(true) }
+            val isError = remember { mutableStateOf(true) }
             TextField(
                 value = "test",
                 onValueChange = {},
@@ -1732,7 +1677,7 @@
                     TextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") }
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                     )
                     Divider(Modifier.fillMaxHeight())
                 }
@@ -1756,7 +1701,7 @@
                     TextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") },
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                         leadingIcon = { Icon(Icons.Default.Favorite, null) }
                     )
                     Divider(Modifier.fillMaxHeight())
@@ -1781,7 +1726,7 @@
                     TextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") },
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                         trailingIcon = { Icon(Icons.Default.Favorite, null) }
                     )
                     Divider(Modifier.fillMaxHeight())
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
index 939ade7..0f76f4d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
@@ -23,7 +23,9 @@
 import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
@@ -61,6 +63,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.coerceAtLeast
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.lerp
 import androidx.compose.ui.unit.offset
 import androidx.compose.ui.util.lerp
 import kotlin.math.max
@@ -562,6 +565,8 @@
                 Box(
                     Modifier
                         .layoutId(PrefixId)
+                        .heightIn(min = MinTextLineHeight)
+                        .wrapContentHeight()
                         .padding(start = startPadding, end = PrefixSuffixTextPadding)
                 ) {
                     prefix()
@@ -571,36 +576,51 @@
                 Box(
                     Modifier
                         .layoutId(SuffixId)
+                        .heightIn(min = MinTextLineHeight)
+                        .wrapContentHeight()
                         .padding(start = PrefixSuffixTextPadding, end = endPadding)
                 ) {
                     suffix()
                 }
             }
 
-            val textPadding = Modifier.padding(
-                start = if (prefix == null) startPadding else 0.dp,
-                end = if (suffix == null) endPadding else 0.dp,
-            )
+            val textPadding = Modifier
+                .heightIn(min = MinTextLineHeight)
+                .wrapContentHeight()
+                .padding(
+                    start = if (prefix == null) startPadding else 0.dp,
+                    end = if (suffix == null) endPadding else 0.dp,
+                )
 
             if (placeholder != null) {
-                placeholder(Modifier.layoutId(PlaceholderId).then(textPadding))
+                placeholder(Modifier
+                    .layoutId(PlaceholderId)
+                    .then(textPadding))
             }
 
             Box(
-                modifier = Modifier.layoutId(TextFieldId).then(textPadding),
+                modifier = Modifier
+                    .layoutId(TextFieldId)
+                    .then(textPadding),
                 propagateMinConstraints = true
             ) {
                 textField()
             }
 
             if (label != null) {
-                Box(modifier = Modifier.layoutId(LabelId)) { label() }
+                Box(Modifier
+                    .heightIn(min = lerp(
+                        MinTextLineHeight, MinFocusedLabelLineHeight, animationProgress))
+                    .wrapContentHeight()
+                    .layoutId(LabelId)) { label() }
             }
 
             if (supporting != null) {
                 @OptIn(ExperimentalMaterial3Api::class)
                 Box(Modifier
                     .layoutId(SupportingId)
+                    .heightIn(min = MinSupportingTextLineHeight)
+                    .wrapContentHeight()
                     .padding(TextFieldDefaults.supportingTextPadding())
                 ) { supporting() }
             }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
index ddb51f2e..7010bcf 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
@@ -24,7 +24,9 @@
 import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
@@ -59,6 +61,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.coerceAtLeast
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.lerp
 import androidx.compose.ui.unit.offset
 import kotlin.math.max
 import kotlin.math.roundToInt
@@ -558,6 +561,8 @@
                 Box(
                     Modifier
                         .layoutId(PrefixId)
+                        .heightIn(min = MinTextLineHeight)
+                        .wrapContentHeight()
                         .padding(start = startPadding, end = PrefixSuffixTextPadding)
                 ) {
                     prefix()
@@ -567,6 +572,8 @@
                 Box(
                     Modifier
                         .layoutId(SuffixId)
+                        .heightIn(min = MinTextLineHeight)
+                        .wrapContentHeight()
                         .padding(start = PrefixSuffixTextPadding, end = endPadding)
                 ) {
                     suffix()
@@ -577,19 +584,29 @@
                 Box(
                     Modifier
                         .layoutId(LabelId)
+                        .heightIn(min = lerp(
+                            MinTextLineHeight, MinFocusedLabelLineHeight, animationProgress))
+                        .wrapContentHeight()
                         .padding(start = startPadding, end = endPadding)) { label() }
             }
 
-            val textPadding = Modifier.padding(
-                start = if (prefix == null) startPadding else 0.dp,
-                end = if (suffix == null) endPadding else 0.dp,
-            )
+            val textPadding = Modifier
+                .heightIn(min = MinTextLineHeight)
+                .wrapContentHeight()
+                .padding(
+                    start = if (prefix == null) startPadding else 0.dp,
+                    end = if (suffix == null) endPadding else 0.dp,
+                )
 
             if (placeholder != null) {
-                placeholder(Modifier.layoutId(PlaceholderId).then(textPadding))
+                placeholder(Modifier
+                    .layoutId(PlaceholderId)
+                    .then(textPadding))
             }
             Box(
-                modifier = Modifier.layoutId(TextFieldId).then(textPadding),
+                modifier = Modifier
+                    .layoutId(TextFieldId)
+                    .then(textPadding),
                 propagateMinConstraints = true,
             ) {
                 textField()
@@ -599,6 +616,8 @@
                 @OptIn(ExperimentalMaterial3Api::class)
                 Box(Modifier
                     .layoutId(SupportingId)
+                    .heightIn(min = MinSupportingTextLineHeight)
+                    .wrapContentHeight()
                     .padding(TextFieldDefaults.supportingTextPadding())
                 ) { supporting() }
             }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt
index e90d4c1..6cfb01f 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt
@@ -422,5 +422,8 @@
 internal val HorizontalIconPadding = 12.dp
 internal val SupportingTopPadding = 4.dp
 internal val PrefixSuffixTextPadding = 2.dp
+internal val MinTextLineHeight = 24.dp
+internal val MinFocusedLabelLineHeight = 16.dp
+internal val MinSupportingTextLineHeight = 16.dp
 
 internal val IconDefaultSizeModifier = Modifier.defaultMinSize(48.dp, 48.dp)
\ No newline at end of file