Description
When TabItem is given a constrained width (e.g., Modifier.widthIn(max = 160.dp)) and the text content is long, the trailing composable is pushed out of the visible area and becomes invisible. This is because the internal Row does not constrain the width of the text slot, allowing it to consume all available horizontal space before trailing is laid out.
Expected Behavior
The trailing (and icon) slots should always remain visible within the constrained width. The text slot should occupy only the remaining space after icon and trailing are measured, and truncate with ellipsis if necessary.
Actual Behavior
When text content is long enough, it takes up the entire row width, causing trailing to be laid out off-screen — even though the TabItem itself has a maximum width constraint.
Reproduction
TabItem(
selected = true,
onSelectedChanged = {},
text = {
Text(
text = "This is a very long tab label",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
modifier = Modifier.widthIn(max = 160.dp).animateItem(),
trailing = {
Icon(imageVector = Icons.Default.Close, contentDescription = "Close")
}
)
Result: The Close icon is not visible. Even though TextOverflow.Ellipsis is set on the Text, it has no effect because the Text is not width-constrained by its parent — the Row gives it unbounded horizontal space, causing it to overflow the TabItem bounds instead of truncating.
Root Cause
In the current implementation:
Row(...) {
icon()
text() // ← measured with remaining width, but not capped
trailing()
}
The Row measures children sequentially. text() can claim all remaining space (or even more if the Row doesn't enforce max width per child), leaving no room for trailing().
Suggested Fix
Wrap the text slot with Modifier.weight(1f, fill = false) so it is measured last, filling only the space remaining after icon and trailing:
content = {
Row(
horizontalArrangement = TabItemContentArrangement,
verticalAlignment = Alignment.CenterVertically
) {
icon()
- text()
+ Box(modifier = Modifier.weight(1f, fill = false)) {
+ text()
+ }
trailing()
}
}
With this change:
icon() and trailing() are measured first at their intrinsic size.
text() gets the remaining width and truncates gracefully (if the caller sets TextOverflow.Ellipsis).
fill = false ensures that short text does not unnecessarily stretch the text slot.
Current Workaround
As a caller, the only workaround is to manually constrain the text composable with a hard-coded max width (e.g., Modifier.widthIn(max = 100.dp)), which is fragile, does not account for varying icon/trailing sizes, and breaks on different screen sizes.
Environment:
Component version: v0.1.0
Compose version: 1.11.0-beta01
Description
When TabItem is given a constrained width (e.g., Modifier.widthIn(max = 160.dp)) and the text content is long, the trailing composable is pushed out of the visible area and becomes invisible. This is because the internal Row does not constrain the width of the text slot, allowing it to consume all available horizontal space before trailing is laid out.
Expected Behavior
The trailing (and icon) slots should always remain visible within the constrained width. The text slot should occupy only the remaining space after icon and trailing are measured, and truncate with ellipsis if necessary.
Actual Behavior
When text content is long enough, it takes up the entire row width, causing trailing to be laid out off-screen — even though the TabItem itself has a maximum width constraint.
Reproduction
Result: The Close icon is not visible. Even though TextOverflow.Ellipsis is set on the Text, it has no effect because the Text is not width-constrained by its parent — the Row gives it unbounded horizontal space, causing it to overflow the TabItem bounds instead of truncating.
Root Cause
In the current implementation:
The Row measures children sequentially. text() can claim all remaining space (or even more if the Row doesn't enforce max width per child), leaving no room for trailing().
Suggested Fix
Wrap the text slot with Modifier.weight(1f, fill = false) so it is measured last, filling only the space remaining after icon and trailing:
With this change:
icon() and trailing() are measured first at their intrinsic size.
text() gets the remaining width and truncates gracefully (if the caller sets TextOverflow.Ellipsis).
fill = false ensures that short text does not unnecessarily stretch the text slot.
Current Workaround
As a caller, the only workaround is to manually constrain the text composable with a hard-coded max width (e.g., Modifier.widthIn(max = 100.dp)), which is fragile, does not account for varying icon/trailing sizes, and breaks on different screen sizes.
Environment:
Component version: v0.1.0
Compose version: 1.11.0-beta01