Skip to content

TabItem trailing content gets clipped when text is long within constrained width #154

@zerofancy

Description

@zerofancy

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions