Skip to content

flex-1 + justify-between: Flexible wrapping breaks space distribution in Row #45

@anilcancakir

Description

@anilcancakir

Bug Description

When a WDiv flex-row uses justify-between alongside children that have flex-1, the space distribution doesn't work correctly — non-flex children are not pushed to the right edge. Instead, they sit immediately adjacent to the flex-1 child.

Observed behavior: First render may show correct positioning, then children shift left on rebuild.

Expected behavior: flex-1 child fills remaining space (via Expanded), non-flex children stay at the right edge — matching CSS justify-content: space-between with flex: 1 behavior.

Reproduction

WDiv(
  className: 'w-full flex flex-row items-center justify-between gap-4',
  children: [
    // Title section — should fill remaining space
    WDiv(
      className: 'flex-1 min-w-0',
      child: WText('Long title that should truncate...', className: 'truncate'),
    ),
    // Actions — should be pushed to the right edge
    WDiv(
      className: 'shrink-0',
      child: WText('Action'),
    ),
  ],
)

Workaround: Replace WDiv Row with native Flutter Row + Expanded:

Row(children: [Expanded(child: titleWidget), actionWidget])

Root Cause

In w_div.dart lines 404-421, when needsSpaceDistribution is true (i.e. justify-between, space-around, or space-evenly), all children are wrapped with Flexible:

final needsFlexible = needsSpaceDistribution || hasOverflowClip;
final rowChildren = needsFlexible
    ? gappedChildren.map((child) {
        if (child is SizedBox || child is Flexible || child is Expanded) {
          return child;
        }
        if (child is WDiv && _hasFlexClass(child.className)) {
          return child;
        }
        // ...
        return Flexible(child: child); // ← wraps ALL non-flex children
      }).toList()
    : gappedChildren;

Why this breaks

  1. The flex-1 child correctly becomes Expanded(flex: 1)tight fit, takes all remaining space.
  2. Non-flex children (e.g. shrink-0 actions) get wrapped with Flexible(child: ...)loose fit, flex: 1 by default.
  3. Now the Row has two flex children — one Expanded(flex: 1) and one Flexible(flex: 1). Flutter distributes space equally between them by flex ratio, making both compete for the same space.
  4. justify-between becomes irrelevant because there's no non-flex space left to distribute — all children are flex participants.

Additional issue: shrink-0 semantics

In flexbox_grid_parser.dart line 112:

'shrink-0': FlexFit.tight, // flex-shrink: 0 (don't shrink)

shrink-0 maps to FlexFit.tight, but CSS flex-shrink: 0 means "don't shrink below intrinsic size" — it should prevent the child from being wrapped in Flexible at all, not set it to tight fit. A tight Flexible forces the child to fill its flex allocation, which is the opposite of "keep intrinsic size."

Suggested Fix

When needsSpaceDistribution is true, don't blindly wrap all children with Flexible. Instead:

  1. Skip wrapping children that have shrink-0 (they should keep their intrinsic size)
  2. Only wrap children that explicitly opt into shrinking
  3. Respect that flex-1 children already handle their own Expanded wrapping
final rowChildren = needsFlexible
    ? gappedChildren.map((child) {
        if (child is SizedBox || child is Flexible || child is Expanded) {
          return child;
        }
        // Skip flex-N children (they self-wrap with Expanded)
        if (child is WDiv && _hasFlexClass(child.className)) {
          return child;
        }
        // Skip shrink-0 children (should not shrink)
        if (child is WDiv && _hasShrinkZero(child.className)) {
          return child;
        }
        return Flexible(child: child);
      }).toList()
    : gappedChildren;

Environment

  • Wind UI: ^1.0.0-alpha.4
  • Flutter: 3.41.6
  • Dart SDK: ^3.11.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions